Why trigger query fails in the sqlite - QT here -? - c++

I am a beginner in the SQL. I am using sqlite , QT - on embedded systems. I want to put a trigger here. The trigger is that whenever the primary key Id is greater than 32145, then channelNum=101 should be set. I want to set the attrib name - text also, but I got the compilation issue. I believe that the setting of trigger is the part of DDL - Data definition language. Please let me know that if I am wrong here. Here is my create db code. I get the sql query error. Also please do suggest how to set the text - attrib = "COmedy".
/** associate db with query **/
QSqlQuery query ( m_demo_db );
/** Foreign keys are disabled by default in sqlite **/
/** Here is the pragma to turn them on first **/
query.exec("PRAGMA foreign_keys = ON;");
if ( false == query.exec())
{
qDebug()<<"Pragma failed";
}
/** Create Table for storing user preference LCN for DTT **/
qDebug()<<"Create Table postcode.db";
query.prepare(" CREATE TABLE dttServiceList (Id INTEGER PRIMARY KEY, attrib varchar(20), channelNum integer )" );
if ( false == query.exec())
{
qDebug()<<"Create dttServiceList table failed";
}
/** Try placing trigger here **/
triggerQuery = "CREATE TRIGGER upd_check BEFORE INSERT ON dttServiceList \
FOR EACH ROW \
BEGIN \
IF Id > 32145 THEN SET channelNum=101; \
END IF; \
END; ";
query.prepare(triggerQuery);
if ( false == query.exec())
{
qDebug()<<"Trigger failed !!";
qDebug() << query.lastError();
}
Also, how to set the text name in the trigger - I want to SET attrib = "Comedy". I am using qt - sqlite. Thanks! for your replies.

SQLite has neither an IF nor a SET statement.
As shown in the documentation, you can use only UPDATE/INSERT/DELETE/SELECT statements in a trigger.
A condition for the entire trigger can be implemented with a WHEN clause.
You cannot change the values being inserted directly; you have to update that records afterwards:
CREATE TRIGGER upd_check
AFTER INSERT ON dttServiceList
FOR EACH ROW
WHEN NEW.Id > 32145
BEGIN
UPDATE dttServiceList
SET channelNum = 101
WHERE Id = NEW.Id;
END;

Related

QSqlQuery fails to get SQL Server stored procedure output value

I am converting web server code written in VB.NET to Qt 5.15.12. The server accesses a Microsoft SQL Server 2012 database. I have several stored procedures that take an output parameter. The output parameter works as expected with VB.net. One stored procedure is giving me issues in Qt. The stored procedure checks if a record exists in a table. If not, it adds it. If so, it updates it. The stored procedure initially returned ##ROWCOUNT but I could not figure out how to get Qt to give me the stored procedure return value so I added an output parameter. The stored procedure essentially looks like this:
CREATE PROCEDURE AddUpdateRecord
#Param1 NChar(12),
#Param2 NVarChar(80),
#RetVal Int Output
AS
BEGIN
SET NOCOUNT ON
SET #RetVal = 0
SELECT Column1 FROM MyTable WHERE Column1=#Param1
IF ##ROWCOUNT > 0
BEGIN
UPDATE MyTable SET Column2=#Param2 WHERE Column1=#Param1
SET #RetVal = ##ROWCOUNT
END
ELSE
BEGIN
INSERT INTO MyTable (Column1, Column2) VALUES (#Param1, #Param2)
SET #RetVal = ##ROWCOUNT
END
END
The Qt code that calls the stored procedure looks like this:
QSqlQuery qry(dbObject);
qry.prepare("execute AddUpdateRecord ?, ?, ?);
qry.bindValue(0, "012345678912");
qry.bindValue(1, "ABCDEFGHIJKLMNOP");
qry.bindValue(2, QVariant(int(-1)), QSql::Out);
if ( qry.exec() && (qry.boundValue(2).toInt() > 0) )
{
return(true);
}
return(false);
If I call the stored procedure with a key that does NOT exist, I successfully get the output value. If I call it with a key that DOES exist, the bound value does not change from -1. Am I doing something wrong that is preventing Qt from getting the output value or is this a bug in Qt?
A bare SELECT in the body of the stored procedure sends a resultset to the client.
SELECT Column1 FROM MyTable WHERE Column1=#Param1
The output parameter is sent after the resultset in the TDS response, and in many client libraries you must consume the resultset before checking the output parameters. You can avoid this and improve this procedure by removing a race condition like this:
CREATE PROCEDURE AddUpdateRecord
#Param1 NChar(12),
#Param2 NVarChar(80),
#RetVal Int Output
AS
BEGIN
SET NOCOUNT ON
SET #RetVal = 0
BEGIN TRANSACTION
IF EXISTS (SELECT * FROM MyTable with (updlock,holdlock) WHERE Column1=#Param1)
BEGIN
UPDATE MyTable SET Column2=#Param2 WHERE Column1=#Param1
SET #RetVal = ##ROWCOUNT
END
ELSE
BEGIN
INSERT INTO MyTable (Column1, Column2) VALUES (#Param1, #Param2)
SET #RetVal = ##ROWCOUNT
END
COMMIT TRANSACTION
END

Query breaks whenever I add MAX() functions or grouping statements

I have some tables laid out like so:
Airplane
(airplaneID number(2) primary key, airplaneName char(20), cruisingRange number(5));
Flights
(airplaneID number (2), flightNo number(4) primary key,
fromAirport char(20), toAirport char(20), distance number(4), depart timestamp,
arrives timestamp, foreign key (airplaneID) references Airplane);
Employees
(employeeID number(10) primary key, employeeName char(18), salary number(7));
Certified
(employeeID number(10), airplaneID number(2),
foreign key (airplaneID) references Airplane,
foreign key (employeeID) references Employees );
And I need to write a query to get the following information:
For each pilot who is certified for at least 4 airplanes, find the
employeeName and the maximum cruisingRange of the airplanes for which
that pilot is certified.
The query I have written is this:
SELECT Employees.employeeName, MAX(Airplane.cruisingRange)
FROM Employees
JOIN Certified ON Employees.employeeID = Certified.employeeID
JOIN Airplane ON Airplane.airplaneID = Certified.airplaneID
GROUP BY Employees.employeeName
HAVING COUNT(*) > 3
Lastly, this is the function that executes and reads in the query information:
void prepareAndExecuteIt() {
// Prepare the query
//sqlQueryToRun.len = strlen((char *) sqlQueryToRun.arr);
exec sql PREPARE dbVariableToHoldQuery FROM :sqlQueryToRun;
/* The declare statement, below, associates a cursor with a
* PREPAREd statement.
* The cursor name, like the statement
* name, does not appear in the Declare Section.
* A single cursor name can not be declared more than once.
*/
exec sql DECLARE cursorToHoldResultTuples cursor FOR dbVariableToHoldQuery;
exec sql OPEN cursorToHoldResultTuples;
int i = 0;
exec sql WHENEVER NOT FOUND DO break;
while(1){
exec sql FETCH cursorToHoldResultTuples INTO empName, cruiseRange;
printf("%s\t", empName);
printf("%s\n", cruiseRange);
i++;
// This is temporary while I debug so it doesn't just loop on forever when the query breaks.
if (i > 500){
printf("Entered break statement\n");
break;
}
}
exec sql CLOSE cursorToHoldResultTuples;
}
The query works until I add the MAX(), GROUP BY, and HAVING statements. Then it just reads in nothing infinitely. I don't know if this is an issue with the way I've written my query or if it's an issue with the C++ code that executes it. I'm using the ProC interface to access an Oracle database. Any ideas as to what's going wrong?
You can't mix implicit and explicit joins. I suggest
SELECT Employees.employeeName, MAX(Airplane.cruisingRange)
FROM Employees
JOIN Certified ON Employees.employeeID = Certified.employeeID
JOIN Airplane ON Airplane.airplaneID = Certified.airplaneID
GROUP BY Employees.employeeName
HAVING COUNT(*) > 3
which works fine.
db<>fiddle here

Getting SQLite DELETE LIKE result [duplicate]

This question already has answers here:
Return deleted rows in sqlite
(3 answers)
Closed 3 years ago.
I want to delete several rows from a table by using DELETE LIKE query in C++. I know how to do it and it works. But also I want to know which rows were actually deleted. Is there a way to do it via 1 query or it is impossible and the only way to do it is
1. SELECT LIKE
2. DELETE LIKE
As alternative to SELECT -> DELETE you could also use a TRIGGER.
The following example shows/demonstrates a relatively generic way (a little inefficient as such) for both :-
DROP TABLE IF EXISTS mytable;
DROP TABLE IF EXISTS mytable_deletions;
DROP TABLE IF EXISTS mytable_deletions2;
DROP TRIGGER IF EXISTS mytable_deletions_trigger;
/* MAIN TABLE */
CREATE TABLE IF NOT EXISTS mytable(id INTEGER PRIMARY KEY, mydata TEXT);
/* CREATE TABLES TO RECORD DELETIONS (first for 1. select->delete the other for 2. trigger)*/
/* Note that these are created based upon the source table, with an additional column srowid so as to be able to definitively delete rows (extra column may not be needed) */
CREATE /*TEMP option? */ TABLE IF NOT EXISTS mytable_deletions AS SELECT 0 AS srowid,* FROM mytable WHERE mydata <> mydata;
CREATE /*TEMP option? */ TABLE IF NOT EXISTS mytable_deletions2 AS SELECT 0 AS srowid,* FROM mytable WHERE mydata <> mydata;
/* Create the BEFORE DELETE Trigger */
CREATE TRIGGER IF NOT EXISTS mytable_deletions_trigger BEFORE DELETE ON mytable
BEGIN
INSERT INTO mytable_deletions2 SELECT rowid AS srowid,* FROM mytable WHERE rowid = old.rowid;
END
;
/* LOAD TESTING DATA */
INSERT INTO mytable (mydata) VALUES('A'),('B'),('C'),('D'),('E');
/* CLEAR PREVIOUS DELETIONS */
DELETE FROM mytable_deletions;
DELETE FROM mytable_deletions2;
/* 1. RECORD DELETIONS PRIOR AND DELETE ACCORDING TO RECORDED DELETIONS */
INSERT INTO mytable_deletions SELECT rowid AS srowid,* FROM mytable WHERE mydata >= 'C';
/* DO THE ACTUAL DELETIONS (will fire trigger 2.) */
DELETE FROM mytable WHERE rowid IN (SELECT srowid FROM mytable_deletions);
/* Display results from TRIGGER */
SELECT * FROM mytable;
SELECT * FROM mytable_deletions;
SELECT * FROM mytable_deletions2;
/* CLEANUP */
DROP TABLE IF EXISTS mytable;
DROP TABLE IF EXISTS mytable_deletions;
DROP TABLE IF EXISTS mytable_deletions2;
DROP TRIGGER IF EXISTS mytable_deletions_trigger;
Running the above results in :-
Remaining data :-
Deleted Rows (select->delete)
Deleted Rows (trigger)

Firebird/IBPP: How to retrieve ID generated by a database autoincrement?

I configured my firebird database to autoincrement the primary key of the table.
CREATE GENERATOR GEN_CHANNEL_PARAMETER_SET_ID;
SET GENERATOR GEN_CHANNEL_PARAMETER_SET_ID TO 0;
CREATE TRIGGER CHANNEL_PARAMETER_SETS_BI FOR CHANNEL_PARAMETER_SETS
ACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
if (NEW.CHANNEL_PARAMETER_SET_ID is NULL) then NEW.CHANNEL_PARAMETER_SET_ID = GEN_ID(GEN_CHANNEL_PARAMETER_SET_ID, 1);
END
Now, in my C++ program using IBPP I have the following problem:
When inserting a dataset into an new row of this table I know all values in my C++ program exept the new primary key because the database creates it. How can I retrieve this key form the database?
Maybe someone else inserted an entry too - just a moment after I inserted one. So retrieve the PK with the highest value could create an error. How can I handle this?
Adopting Amir Rahimi Farahani's answer I found the following solution for my problem:
I use a generator:
CREATE GENERATOR GEN_CHANNEL_PARAMETER_SET_ID;
SET GENERATOR GEN_CHANNEL_PARAMETER_SET_ID TO 0;
and the following C++/IBPP/SQL code:
// SQL statement
m_DbStatement->Execute(
"SELECT NEXT VALUE FOR gen_channel_parameter_set_id FROM rdb$database"
);
// Retrieve Data
IBPP::Row ibppRow;
int64_t channelParameterSetId;
m_DbStatement->Fetch(ibppRow);
ibppRow->Get (1, channelParameterSetId);
// SQL statement
m_DbStatement->Prepare(
"INSERT INTO channel_parameter_sets "
"(channel_parameter_set_id, ...) "
"VALUES (?, ...) "
);
// Set variables
m_DbStatement->Set (1, channelParameterSetId);
...
...
// Execute
m_DbStatement->Execute ();
m_DbTransaction->CommitRetain ();
It is possible to generate and use the new id before inserting the new record:
SELECT NEXT VALUE FOR GEN_CHANNEL_PARAMETER_SET_ID FROM rdb$database
You now know the value for new primary key.
Update:
IBPP supports RETURNING too:
// SQL statement
m_DbStatement->Prepare(
"INSERT INTO channel_parameter_sets "
"(...) VALUES (...) RETURNING channel_parameter_set_id"
);
// Execute
m_DbStatement->Execute ();
m_DbTransaction->CommitRetain ();
// Get the generated id
m_DbStatement->Get (1, channelParameterSetId);
...
To retrieve the value of the generated key (or any other column) you can use INSERT ... RETURNING ....
For example:
INSERT INTO myTable (x, y, z) VALUES (1, 2, 3) RETURNING ID
Also a lot of drivers provide extra features to support RETURNING, but I don't know IBPP.
Note that from the perspective of a driver the use of RETURNING will make the insert act like an executable stored procedure; some drivers might require you to execute it in a specific way.

how to write an insert query in Doctrine

How do I create an insert query in Doctrine that will perform the same function as the following SQL query:
INSERT INTO target (tgt_col1, tgt_col2)
SELECT 'flag' as marker, src_col2 FROM source
WHERE src_col1='mycriteria'
Doctrine documentation says:
If you want to execute DELETE, UPDATE or INSERT statements the Native
SQL API cannot be used and will probably throw errors. Use
EntityManager#getConnection() to access the native database connection
and call the executeUpdate() method for these queries.
Examples
// Get entity manager from your context.
$em = $this->getEntityManager();
/**
* 1. Raw query
*/
$query1 = "
INSERT INTO target (tgt_col1, tgt_col2)
SELECT 'flag' as marker, src_col2 FROM source
WHERE src_col1='mycriteria'
";
$affectedRows1 = $em->getConnection()->executeUpdate($query1);
/**
* 2. Query using class metadata.
*/
$metadata = $em->getClassMetadata(Your\NameSpace\Entity\Target::class);
$tableName = $metadata->getTableName();
$niceTitle = $metadata->getColumnName('niceTitle');
$bigDescription = $metadata->getColumnName('bigDescription');
$metadata2 = $em->getClassMetadata(Your\NameSpace\Entity\Source::class);
$table2Name = $metadata2->getTableName();
$smallDescription = $metadata2->getColumnName('smallDescription');
$query2 = "
INSERT INTO $tableName ($niceTitle, $bigDescription)
SELECT 'hardcoded title', $smallDescription FROM $table2Name
WHERE $niceTitle = 'mycriteria'
";
$affectedRows2 = $em->getConnection()->executeUpdate($query2);
I'm still not convinced it's the right approach you are taking but if you really need an SQL query to be run for whatever reason you can do that in Doctrine with $entityManager->createNativeQuery(); function:
http://doctrine-orm.readthedocs.org/en/latest/reference/native-sql.html
Doctrine isn't a tool for query manipulation. The whole idea is to work on Entity level, not the SQL level (tables, etc). Doctrine's 2 QueryBuilder doesn't even support INSERT operations via DQL.
A small snippet of pseudo code below to illustrate how it can be done in "Doctrine's way":
$qb = $entityManager->createQueryBuilder();
$qb->select('s')
->from('\Foo\Source\Entity', 's')
->where('s.col1 = :col1')
->setParameter('col1', 'mycriteria');
$sourceEntities = $qb->getQuery()->getResult();
foreach($sourceEntities as $sourceEntity) {
$targetEntity = new \Foo\Target\Entity();
$targetEntity->col1 = $sourceEntity->col1;
$targetEntity->col2 = $sourceEntity->col2;
$entityManager->persist($targetEntity);
}
$entityManager->flush();