QSqlQuery fails to get SQL Server stored procedure output value - c++

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

Related

stored procedure in BigQuery had error "Correlated subqueries that reference other tables are not supported...."

I already created stored procedure in BigQuery for data querying.
The main point is send some parameter to get data in WHERE condition, then use list of data in WHERE of other table. It shown error as "Correlated subqueries that reference other tables are not supported unless they can be de-correlated, such as by transforming them into an efficient JOIN."
THIS IS MY QUERY
CALL mydatabase.stored_procedures.GetDateOutCS();
SELECT *, GetDateOutCS(ContainerHeaderID)
FROM mydatabase.instance.PRD_ContainerHeader
THIS IS MY STORED PROCEDURE
CREATE OR REPLACE PROCEDURE mydatabase.stored_procedures.GetDateOutCS()
BEGIN
CREATE TEMP FUNCTION GetDateOutCS (ContainerHeader_ID INT64)
AS
(
( SELECT GIDateFROM mydatabase.instance.RMM_ReceivedMaterialHU
WHERE HU IN
(SELECT CAST(HU AS INT64) FROM mydatabase.instance.PRD_ContainerLine
WHERE ContainerHeaderID=ContainerHeader_ID) LIMIT 1)
);
END;
I've tried to changed subquery to join table, but it's not works for me.
CREATE OR REPLACE PROCEDURE mydatabase.stored_procedures.GetDateOutCS_2()
BEGIN
CREATE TEMP FUNCTION GetDateOutCS (ContainerHID INT64)
AS
(
array( SELECT CAST(GIDate AS DATETIME)
FROM mydatabase.instance.RMM_ReceivedMaterialHU as h
INNER JOIN mydatabase.instance.PRD_ContainerLine as cl ON h.HU = CAST(cl.HU AS INT64)
WHERE cl.ContainerHeaderID=ContainerHIDAND cl.HU IS NOT NULL LIMIT 1)
);
END;
My expect solutions
How to fix this stored procedure.
This is limitation of BigQuery?

Redshift Pivot Function

I've got a similar table which I'm trying to pivot in Redshift:
UUID
Key
Value
a123
Key1
Val1
b123
Key2
Val2
c123
Key3
Val3
Currently I'm using following code to pivot it and it works fine. However, when I replace the IN part with subquery it throws an error.
select *
from (select UUID ,"Key", value from tbl) PIVOT (max(value) for "key" in (
'Key1',
'Key2',
'Key3
))
Question: What's the best way to replace the IN part with sub query which takes distinct values from Key column?
What I am trying to achieve;
select *
from (select UUID ,"Key", value from tbl) PIVOT (max(value) for "key" in (
select distinct "keys" from tbl
))
From the Redshift documentation - "The PIVOT IN list values cannot be column references or sub-queries. Each value must be type compatible with the FOR column reference." See: https://docs.aws.amazon.com/redshift/latest/dg/r_FROM_clause-pivot-unpivot-examples.html
So I think this will need to be done as a sequence of 2 queries. You likely can do this in a stored procedure if you need it as a single command.
Updated with requested stored procedure with results to a cursor example:
In order to make this supportable by you I'll add some background info and description of how this works. First off a stored procedure cannot produce results strait to your bench. It can either store the results in a (temp) table or to a named cursor. A cursor is just storing the results of a query on the leader node where they wait to be fetched. The lifespan of the cursor is the current transaction so a commit or rollback will delete the cursor.
Here's what you want to happen as individual SQL statements but first lets set up the test data:
create table test (UUID varchar(16), Key varchar(16), Value varchar(16));
insert into test values
('a123', 'Key1', 'Val1'),
('b123', 'Key2', 'Val2'),
('c123', 'Key3', 'Val3');
The actions you want to perform are first to create a string for the PIVOT clause IN list like so:
select '\'' || listagg(distinct "key",'\',\'') || '\'' from test;
Then you want to take this string and insert it into your PIVOT query which should look like this:
select *
from (select UUID, "Key", value from test)
PIVOT (max(value) for "key" in ( 'Key1', 'Key2', 'Key3')
);
But doing this in the bench will mean taking the result of one query and copy/paste-ing into a second query and you want this to happen automatically. Unfortunately Redshift does allow sub-queries in PIVOT statement for the reason given above.
We can take the result of one query and use it to construct and run another query in a stored procedure. Here's such a store procedure:
CREATE OR REPLACE procedure pivot_on_all_keys(curs1 INOUT refcursor)
AS
$$
DECLARE
row record;
BEGIN
select into row '\'' || listagg(distinct "key",'\',\'') || '\'' as keys from test;
OPEN curs1 for EXECUTE 'select *
from (select UUID, "Key", value from test)
PIVOT (max(value) for "key" in ( ' || row.keys || ' )
);';
END;
$$ LANGUAGE plpgsql;
What this procedure does is define and populate a "record" (1 row of data) called "row" with the result of the query that produces the IN list. Next it opens a cursor, whose name is provided by the calling command, with the contents of the PIVOT query which uses the IN list from the record "row". Done.
When executed (by running call) this function will produce a cursor on the leader node that contains the result of the PIVOT query. In this stored procedure the name of the cursor to create is passed to the function as a string.
call pivot_on_all_keys('mycursor');
All that needs to be done at this point is to "fetch" the data from the named cursor. This is done with the FETCH command.
fetch all from mycursor;
I prototyped this on a single node Redshift cluster and "FETCH ALL" is not supported at this configuration so I had to use "FETCH 1000". So if you are also on a single node cluster you will need to use:
fetch 1000 from mycursor;
The last point to note is that the cursor "mycursor" now exists and if you tried to rerun the stored procedure it will fail. You could pass a different name to the procedure (making another cursor) or you could end the transaction (END, COMMIT, or ROLLBACK) or you could close the cursor using CLOSE. Once the cursor is destroyed you can use the same name for a new cursor. If you wanted this to be repeatable you could run this batch of commands:
call pivot_on_all_keys('mycursor'); fetch all from mycursor; close mycursor;
Remember that the cursor has a lifespan of the current transaction so any action that ends the transaction will destroy the cursor. If you have AUTOCOMMIT enable in your bench this will insert COMMITs destroying the cursor (you can run the CALL and FETCH in a batch to prevent this in many benches). Also some commands perform an implicit COMMIT and will also destroy the cursor (like TRUNCATE).
For these reasons, and depending on what else you need to do around the PIVOT query, you may want to have the stored procedure write to a temp table instead of a cursor. Then the temp table can be queried for the results. A temp table has a lifespan of the session so is a little stickier but is a little less efficient as a table needs to be created, the result of the PIVOT query needs to be written to the compute nodes, and then the results have to be sent to the leader node to produce the desired output. Just need to pick the right tool for the job.
===================================
To populate a table within a stored procedure you can just execute the commands. The whole thing will look like:
CREATE OR REPLACE procedure pivot_on_all_keys()
AS
$$
DECLARE
row record;
BEGIN
select into row '\'' || listagg(distinct "key",'\',\'') || '\'' as keys from test;
EXECUTE 'drop table if exists test_stage;';
EXECUTE 'create table test_stage AS select *
from (select UUID, "Key", value from test)
PIVOT (max(value) for "key" in ( ' || row.keys || ' )
);';
END;
$$ LANGUAGE plpgsql;
call pivot_on_all_keys();
select * from test_stage;
If you want this new table to have keys for optimizing downstream queries you will want to create the table in one statement then insert into it but this is quickie path.
A little off-topic, but I wonder why Amazon couldn't introduce a simpler syntax for pivot. IMO, if GROUP BY is replaced by PIVOT BY, it can give enough hint to the interpreter to transform rows into columns. For example:
SELECT partname, avg(price) as avg_price FROM Part GROUP BY partname;
can be written as:
SELECT partname, avg(price) as avg_price FROM Part PIVOT BY partname;
Even multi-level pivoting can also be handled in the same syntax.
SELECT year, partname, avg(price) as avg_price FROM Part PIVOT BY year, partname;

How to show other vaues in a selectlist in APEX

I am using Oracle APEX to build a interactive report. There is a field in my database called method which should contain either A or B. In the edit page, I want to show a list containing A and B so that users can choose from those two.
I set the type of the item to SelectList and since I need to add the other value to the list, in the List of Values area, I set the type to PL/SQL Function Body returning SQL Query and the code is as follows:
Begin
select TEST_METHOD into method from table_test
where ROWID = :P2_ROWID;
IF ('Live' = method) THEN
return select 'Screenshots' from dual;
END IF;
return select 'Live' from dual;
End;
However, I got the following error:
ORA-06550: line 5, column 10: PLS-00103: Encountered the symbol "SELECT" when expecting one of the following: ( - + ; case mod new not null continue avg count current exists max min prior sql stddev sum variance execute forall merge time timestamp interval date pipe
I am new to plsql and APEX, I know the code looks wired but I don't know what's wrong. I am also wondering if there is any other way to achieve my goal? Thanks!

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

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;

Transaction in SP avoid correct answer

I have an SP that work very well when called from SSMS. but when I call it from my application
that written in native C++ and use ODBC for connecting to database, the operation return no error but actually do nothing in the database.
My SP read some values from some temporary tables and either insert them in database or update them.
I had a transaction in SP that guard all the code of SP, I hardly debug my SP and find that function will return in first insert or update and so do nothing. So I remove that transaction and function partly worked, I mean it add some of the items but leave some of them there without adding them to the database.
here is a skeleton of my SP:
--BEGIN TRANSACTION
DECLARE #id bigint, #name nvarchar(50)
DELETE FROM MyTable WHERE NOT( id IN (SELECT id from #MyTable) )
DECLARE cur1 CURSOR FOR SELECT id, name FROM #MyTable
OPEN cur1
WHILE 1 != 0
BEGIN
FETCH cur1 INTO #id, #name
IF ##FETCH_STATUS != 0 BREAK;
UPDATE MyTable SET [Name]=#name WHERE [id]=#id
IF ##ROWCOUNT = 0
INSERT INTO MyTable ( ID, Name ) VALUES ( #id, #name )
END
CLOSE cur1
DEALLOCATE cur1
--COMMIT TRANSACTION
Is it possible you have an implicit transaction started in ODBC that needs an explicit COMMIT to end (after the call to the SP)? SSMS generally uses autocommit mode.
I solve my problem by adding SET NOCOUNT ON to start of my SP, so I think when SQL return multiple result set as a result of executing my SQL, ODBC close or cancel the command upon receiving first result set.