Transaction in SP avoid correct answer - c++

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.

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

How to handle error in ibm_db python package while calling stored procedure?

I'm trying call stored procedure using following code
conn = ibm_db.connect("database","username","password")
sql = "CALL DB2INST1.KPI_VALIDATE()"
stmt = ibm_db.exec_immediate(conn, sql)
But this procedure does not return any rows & It will only return code. Now I need to handle error whether procedure run successfully or not. Could anyone help me how to handle this?
Thanks
For test purposes, I've created a table:
db2 "create table so(c1 int not null primary key)"
and my procedure will simply insert a row into this table - this will allow me to easily force an error with a duplicate key:
db2 "create or replace procedure so_proc(in insert_val int)
language sql
insert into so values(insert_val)"
db2 "call so_proc(1)"
Return Status = 0
db2 "call so_proc(1)"
SQL0803N One or more values in the INSERT statement, UPDATE statement, or
foreign key update caused by a DELETE statement are not valid because the
primary key, unique constraint or unique index identified by "1" constrains
table "DB2V115.SO" from having duplicate values for the index key.
SQLSTATE=23505
now with Python:
conn = ibm_db.connect("DATABASE=SAMPLE;HOSTNAME=localhost;PORT=61115;UID=db2v115;PWD=xxxxx;","","")
stmt = ibm_db.exec_immediate(conn, "CALL SO_PROC(2)")
stmt = ibm_db.exec_immediate(conn, "CALL SO_PROC(2)")
Exception Traceback (most recent call last)
<ipython-input-8-c1f4b252e70a> in <module>
----> 1 stmt = ibm_db.exec_immediate(conn, "CALL SO_PROC(2)")
Exception: [IBM][CLI Driver][DB2/LINUXX8664] SQL0803N One or more values in the INSERT statement, UPDATE statement, or foreign key update caused by a DELETE statement are not valid because the primary key, unique constraint or unique index identified by "1" constrains table "DB2V115.SO" from having duplicate values for the index key. SQLSTATE=23505 SQLCODE=-803
so if a procedure hits an exception then you'll get it, you just need to handle exception Try/Except block:
try:
stmt = ibm_db.exec_immediate(conn, "CALL SO_PROC(2)")
except Exception:
print("Procedure failed with sqlstate {}".format(ibm_db.stmt_error()))
print("Error {}".format(ibm_db.stmt_errormsg()))
Procedure failed with sqlstate 23505
Error [IBM][CLI Driver][DB2/LINUXX8664] SQL0803N One or more values in the INSERT statement, UPDATE statement, or foreign key update caused by a DELETE statement are not valid because the primary key, unique constraint or unique index identified by "1" constrains table "DB2V115.SO" from having duplicate values for the index key. SQLSTATE=23505 SQLCODE=-803
Or you are actually interested with CALL return code/status? E.g.:
create or replace procedure so_proc_v2(in insert_val int)
language sql
if not exists (select 1 from so where c1 = insert_val)
then
insert into so values(insert_val);
return 0;
else
return -1;
end if#
test:
db2 "call so_proc_v2(10)"
Return Status = 0
db2 "call so_proc_v2(10)"
Return Status = -1
then this is a bit tricky. With CLI trace enabled (I have ibm_db installed in my local path so it fetched CLI package there too):
export LD_LIBRARY_PATH=$HOME/.local/lib/python3.7/site-packages/clidriver/lib/
$HOME/.local/lib/python3.7/site-packages/clidriver/bin/db2trc on -cli -f /tmp/cli/trc
<run_code>
$HOME/.local/lib/python3.7/site-packages/clidriver/bin/db2trc off
$HOME/.local/lib/python3.7/site-packages/clidriver/bin/db2trc fmt -cli /tmp/cli.trc /tmp/cli.fmt
trace does show the returns status:
SQLExecute( hStmt=1:8 )
---> Time elapsed - -7.762688E+006 seconds
( Row=1, iPar=1, fCType=SQL_C_LONG, rgbValue=10 )
( return=-1 )
( COMMIT REQUESTED=1 )
( COMMIT REPLY RECEIVED=1 )
but I don't see anywhere in python-ibmdb API a way to fetch it... (e.g. ibm_dbcallproc doesn't have such option). Which means, that unless I'm missing something, you would have to raise an issue on Github to extent the API

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!

IF statements in SQL Server triggers

I need to create a SQL Server trigger to block updates and deletes to a table Service.
This action should be done only to Service in which the column States sample data is "completed".
It should allow updates and deletes to Service in which the column States sample data is "active".
This is what I tried, I am having problems with doing the else operation (that is allowing updates to Service in which the column State sample data is "active").
CREATE TRIGGER [Triggername]
ON dbo.Service
FOR INSERT, UPDATE, DELETE
AS
DECLARE #para varchar(10),
#results varchar(50)
SELECT #para = Status
FROM Service
IF (#para = 'completed')
BEGIN
SET #results = 'An invoiced service cannot be updated or deleted!';
SELECT #results;
END
BEGIN
RAISERROR ('An invoiced service cannot be updated or deleted', 16, 1)
ROLLBACK TRANSACTION
RETURN
END
So if I understand you correctly, any UPDATE or DELETE should be allowed if the State column has a value of Active, but stopped in any other case??
Then I'd do this:
CREATE TRIGGER [Triggername]
ON dbo.Service
FOR UPDATE, DELETE
AS
BEGIN
-- if any row exists in the "Deleted" pseudo table of rows that WERE
-- in fact updated or deleted, that has a state that is *not* "Active"
-- then abort the operation
IF EXISTS (SELECT * FROM Deleted WHERE State <> 'Active')
ROLLBACK TRANSACTION
-- otherwise let the operation finish
END
As a note: you cannot easily return messages from a trigger (with SELECT #Results) - the trigger just silently fails by rolling back the currently active transaction

What's the right pattern for unique data in columns?

I've a table [File] that has the following schema
CREATE TABLE [dbo].[File]
(
[FileID] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](256) NOT NULL,
CONSTRAINT [PK_File] PRIMARY KEY CLUSTERED
(
[FileID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
The idea is that the FileID is used as the key for the table and the Name is the fully qualified path that represents a file.
What I've been trying to do is create a Stored Procedure that will check to see if the Name is already in use if so then use that record else create a new record.
But when I stress test the code with many threads executing the stored procedure at once I get different errors.
This version of the code will create a deadlock and throw a deadlock exception on the client.
CREATE PROCEDURE [dbo].[File_Create]
#Name varchar(256)
AS
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION xact_File_Create
SET XACT_ABORT ON
SET NOCOUNT ON
DECLARE #FileID int
SELECT #FileID = [FileID] FROM [dbo].[File] WHERE [Name] = #Name
IF ##ROWCOUNT=0
BEGIN
INSERT INTO [dbo].[File]([Name])
VALUES (#Name)
SELECT #FileID = [FileID] FROM [dbo].[File] WHERE [Name] = #Name
END
SELECT * FROM [dbo].[File]
WHERE [FileID] = #FileID
COMMIT TRANSACTION xact_File_Create
GO
This version of the code I end up getting rows with the same data in the Name column.
CREATE PROCEDURE [dbo].[File_Create]
#Name varchar(256)
AS
BEGIN TRANSACTION xact_File_Create
SET NOCOUNT ON
DECLARE #FileID int
SELECT #FileID = [FileID] FROM [dbo].[File] WHERE [Name] = #Name
IF ##ROWCOUNT=0
BEGIN
INSERT INTO [dbo].[File]([Name])
VALUES (#Name)
SELECT #FileID = [FileID] FROM [dbo].[File] WHERE [Name] = #Name
END
SELECT * FROM [dbo].[File]
WHERE [FileID] = #FileID
COMMIT TRANSACTION xact_File_Create
GO
I'm wondering what the right way to do this type of action is? In general this is a pattern I'd like to use where the column data is unique in either a single column or multiple columns and another column is used as the key.
Thanks
If you are searching heavily on the Name field, you will probably want it indexed (as unique, and maybe even clustered if this is the primary search field). As you don't use the #FileID from the first select, I would just select count(*) from file where Name = #Name and see if it is greater than zero (this will prevent SQL from retaining any locks on the table from the search phase, as no columns are selected).
You are on the right course with the SERIALIZABLE level, as your action will impact subsequent queries success or failure with the Name being present. The reason the version without that set causes duplicates is that two selects ran concurrently and found there was no record, so both went ahead with the inserts (which creates the duplicate).
The deadlock with the prior version is most likely due to the lack of an index making the search process take a long time. When you load the server down in a SERIALIZABLE transaction, everything else will have to wait for the operation to complete. The index should make the operation fast, but only testing will indicate if it is fast enough. Note that you can respond to the failed transaction by resubmitting: in real world situations hopefully the load will be transient.
EDIT: By making your table indexed, but not using SERIALIZABLE, you end up with three cases:
Name is found, ID is captured and used. Common
Name is not found, inserts as expected. Common
Name is not found, insert fails because another exact match was posted within milliseconds of the first. Very Rare
I would expect this last case to be truly exceptional, so using an exception to capture this very rare case would be preferable to engaging SERIALIZABLE, which has serious performance consequences.
If you do really have an expectation that it will be common to have posts within milliseconds of one another of the same new name, then use a SERIALIZABLE transaction in conjunction with the index. It will be slower in the general case, but faster when these posts are found.
First, create a unique index on the Name column. Then from your client code first check if the Name exists by selecting the FileID and putting the Name in the where clause - if it does, use the FileID. If not, insert a new one.
Using the Exists function might clean things up a little.
if (Exists(select * from table_name where column_name = #param)
begin
//use existing file name
end
else
//use new file name