Sybase ASE SP which handles foreign key constraint violation gracefully - stored-procedures

I'm trying to code a Sybase ASE (15) stored procedure which deletes a customer. It is possible that the DELETE failes due to a "foreign key constraint violation", in which case the stored procedure should rollback the transaction and return.
CREATE PROCEDURE dbo.spumb_deleteCustomer #customertodelete int AS BEGIN
BEGIN TRANSACTION TRX_UMBDELCUSTOMER
DELETE CREDITCARDS WHERE CUSTOMERID = #customertodelete
DELETE CUSTOMER_SELECTION_MAP WHERE CUSTOMERID = #customertodelete
DELETE CUSTOMERS WHERE ID = #customertodelete
SELECT #rcnt = ##ROWCOUNT
IF (#rcnt <> 1) BEGIN
PRINT 'FAILED TO DELETE CUSTOMER'
ROLLBACK TRANSACTION TRX_UMBDELCUSTOMER
RETURN
END
COMMIT TRANSACTION TRX_UMBDELCUSTOMER
END
When running this SP in a cursor, execution aborts after the first invalid DELETE. How can I make the cursor continue (or, rather, the SP not raise an error)?
Thanks, Simon

You should check ##error !=0 to indicate an error rather than look for ##rowcount and handle those errors otherwise the calling client may get unexpected messages returned. If you do check ##rowcount you need to save it immediately to a variable after every delete because each command resets it.
If you're specifically looking to pick up foreign key violation messages you can check ##error for 546 or 547 as these will be returned in ##error when it fails:
http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc00729.1500/html/errMessageAdvRes/BABCCECF.htm
http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc00729.1500/html/errMessageAdvRes/BABHJIEC.htm

Related

Rails Postgres Insert Duplicate Row Check Issues

I'm trying to detect and silently discard any duplicate INSERTs into a table, and if a dupe detected, return the ID (Primary Key) of the existing record. Otherwise, insert the record and return the new ID.
I can do this with either a RULE or a TRIGGER, however both have drawbacks. Here's an example of my RULE:
CREATE OR REPLACE RULE territory_products_ignore_duplicate_inserts AS
ON INSERT TO territory_products
WHERE (EXISTS ( SELECT 1
FROM territory_products tp
WHERE tp.territory_id = NEW.territory_id AND tp.product_id = NEW.product_id)) DO INSTEAD SELECT tp.id
FROM territory_products tp WHERE tp.territory_id = NEW.territory_id AND tp.product_id = NEW.product_id LIMIT 1;
Testing this in a SQL console or psql with an INSERT works fine. If there's a dupe, it'll return the first existing record's id and not do the INSERT. Otherwise, it will go ahead with the INSERT. However, in Rails, it fails and returns this error:
ERROR: cannot perform INSERT RETURNING on relation "territory_products"
HINT: You need an unconditional ON INSERT DO INSTEAD rule with a RETURNING clause.
Moving to a TRIGGER, I try this:
CREATE OR REPLACE Function territory_products_ignore_dups() Returns Trigger
As $$
Begin
If Exists (
Select id From territory_products tp
Where tp.territory_id = NEW.territory_id And tp.product_id = NEW.product_id
) Then
Return NULL;
End If;
Return NEW;
End;
$$ Language plpgsql;
Create Trigger territory_products_ignore_dups
Before Insert On territory_products
For Each Row
Execute Procedure territory_products_ignore_dups();
This also works fine, except that I can't get it to return the existing ID, because of the Return NULL (which is required to disallow the INSERT).
Can anyone resolve either of these issues, so I get the result I'm looking for? (e.g. silently discard the INSERT in case of a dupe and return the ID of the existing record. Or if the INSERT is successful, return the ID of the new record).
Add a unique index to your database:
# migration file
add_index : territory_products, [:territory_id, :product_id], unique: true
In your logic look for the existing record or create if doesn't exist. It will raise an exception in case of a race condition. Just search again for the record.
#logic when finding/creating record
begin
TerritoryProduct.where(territory_id: params[:territory_id], product_id: params[:product_id]).first_or_create!
rescue ActiveRecord::RecordNotUnique
# In case it already exists
TerritoryProduct.where(territory_id: params[:territory_id], product_id: params[:product_id]).first
end

db2 how to create a simple update and select Stored Procedure?

DB2 Stored Procedure update record and select record
CREATE PROCEDURE DB2INST1.GETPEOPLE2(IN ids bigint )
SPECIFIC DB2INST1.GETPEOPLE2
DYNAMIC RESULT SETS 1
MODIFIES SQL DATA
LANGUAGE SQL
BEGIN
update test2 set a=a+1 where a>ids;
DECLARE rs1 CURSOR WITH RETURN TO CLIENT FOR
select * from db2inst1.test2;
OPEN rs1;
END
but it's not working.
error: DB21034E The command was processed as an SQL statement because it was not a valid Command Line Processor command. During SQL processing it returned: SQL0104N An unexpected token "rs1 CURSOR sele" was found following "ids; DECLARE". Expected tokens may include: "". LINE NUMBER=10. SQLSTATE=42601
ok,it work:
BEGIN
DECLARE rs1 CURSOR WITH RETURN TO CLIENT FOR
select * from db2inst1.test2;
update test2 set a=a+1 where a>ids;
OPEN rs1;
END

T-SQL Error in non-executing branch of if..else block

I'm hitting my head against the wall with this one.
We have a stored procedure that is being called in an API that we are developing and the stored procedure has the following code:
if(#StatusCode = 41 and #OperationName != 'convert')
Begin
EXEC [uspCreateOrg] #RequestID = #_RequestId
End
else
Begin
EXEC [uspUpsertOrg] #RequestID = #_RequestId
End
Using the profiler, we can that the 'if' branch is the one that gets executed, but we also see that SQL Server is looking down the 'else' branch and calling into that stored procedure and throwing an exception. The uspUpsertOrg procedure calls the DBAmp BulkOps which has the following code in it:
create table #errorlog (line varchar(255))
insert into #errorlog
exec #Result = master..xp_cmdshell #Command
-- print output to msgs
declare #line varchar(255)
declare #printCount int
set #printCount = 0
DECLARE tables_cursor CURSOR FOR SELECT line FROM #errorlog
OPEN tables_cursor
FETCH NEXT FROM tables_cursor INTO #line
WHILE (##FETCH_STATUS <> -1)
BEGIN
if #line is not null
begin
print #line
exec SF_Logger #SPName,N'Message', #Line
set #errorLines = #errorLines + #line
set #printCount = #printCount +1
end
FETCH NEXT FROM tables_cursor INTO #line
END
deallocate tables_cursor
-- drop temp output table
drop table #errorlog
The exception that gets thrown is that the #errorLog table does not exist. So in summary we are getting an exception that a temp table created on the line above the insert does not exist in a stored procedure that does not even get called...Fun...
When we comment out the call to uspUpsertOrg everything works as expected. When we change the temp table to a real table, it still fails, but if we create it outside the procedure and then run the process, it works. In any of these cases, the code does not go down the 'else' branch in the sense that the 'else' branch would be the one that gets executed. It's almost as if SQL server is looking ahead down all code paths--I know that SQL Server does that kind of thing for optimization, etc, but why would it miss the fact that the table IS being created before use? I've done this kind of thing before and never had problems.
Thanks for the help!
According to this article on Execution Plan Basics, this exact scenario causes a problem for the algebrizer that doesn't execute your code, but is responsible for generating the execution plan. Look for the section When the Estimated Plan is Invalid.
I think this workaround will work for you: Instead of the CREATE statement, use
SELECT CAST('' as VARCHAR(255)) as line INTO #errorlog

BEFORE INSERT trigger with stored procedure call (DB2 LUW 9.5)

I am trying to create a BEFORE INSERT trigger that will check the incoming value of a field, and replace it with the same field in another row if that the field is null. However, when I add the CALL statement to my trigger, an error is returned "The trigger "ORGSTRUCT.CSTCNTR_IN" is defined with an unsupported triggered SQL statement". I checked the documentation and saw that cursors weren't supported in the BEFORE (part of the reason for making the stored procedure in the first place), but even when I remove the cursor declaration from the stored procedure the call still generates the same error.
Trigger:
CREATE TRIGGER orgstruct.cstcntr_IN
NO CASCADE
BEFORE INSERT ON orgstruct.tOrgs
REFERENCING NEW AS r
FOR EACH ROW MODE DB2SQL
BEGIN ATOMIC
DECLARE prnt_temp BIGINT;
DECLARE cstcntr_temp CHAR(11);
SET prnt_temp = r.prnt;
SET cstcntr_temp = r.cstcntr;
CALL orgstruct.trspGetPrntCstCntr(prnt_temp,cstcntr_temp);
SET r.cstcntr = cstcntr_temp;
END
Stored procedure:
CREATE PROCEDURE orgstruct.trspGetPrntCstCntr (
IN p_prnt BIGINT,
OUT p_cstcntr CHAR(11)
)
SPECIFIC trGetPrntCstCntr
BEGIN
IF p_prnt IS NULL THEN
RETURN;
END IF;
BEGIN
DECLARE c1 CURSOR
FOR
SELECT cstcntr
FROM orgstruct.tOrgs
WHERE id = p_prnt
FOR READ ONLY;
OPEN c1;
FETCH FROM c1 INTO p_cstcntr;
CLOSE c1;
END;
END
According to the documentation, CALL is allowed in a BEFORE trigger, so I don't understand what the problem is.
A before trigger can call a stored procedure, but the stored proc can't do anything not allowed in the trigger.
In your case, the default level of data access for a SQL stored proc is MODIFIES SQL DATA, which is not allowed in the trigger. You could recreate your stored procedure, changing the data access level to READS SQL DATA; this will allow you to create the trigger.
However: There is no reason to call a stored procedure for something this simple; You can do it using a simple inline trigger:
create trigger orgstruct.cstcntr_IN
no cascade
before insert on orgstruct.tOrgs
referencing new as r
for each row
mode db2sql
set r.cstcntr = case
when r.p_prnt is not null
then (select cstcntr from tOrgs where id = r.p_prnt fetch first 1 row only)
else r.cstcntr
end;
This will be a LOT more efficient because it eliminates both the stored procedure call and the cursor processing inside the stored proc. Even if you wanted to use the stored proc, you could eliminate the cursor inside the stored proc and improve performance.
FYI: the logic that you posted contains an error, and will always set CSTCNTR to NULL. The trigger posted in this answer not do this. :-)

vb6 does not recognize the error when there is a result set

I have a stored procedure which
returns a result set with the error
message, when an error occurs. If it
executes without any error, the result
set is empty (Command(s) completed
successfully)
On the vb6 side, I execute the sp and
check whether there is an error by
If Err <> 0 Then
' do sth
End If
But, when there is a result set, the
Err is always 0.
How should I handle this situation?
Sorry for inadequate explanation.
Here are my scripts:
--my sample table
create table #InvoiceDocument (InvoiceID int, ItemID int, Price float, DocStatus bit)
--my test values
insert into #InvoiceDocument (InvoiceID, ItemID, Price)
values (1, 1, 2.5), (1, 2, 5.0), (1,5, null)
--my sample procedure
create procedure sp_ApproveInvoice #InvoiceID int
as
begin
set nocount on
select * into #temp
from #InvoiceDocument
where Price is null and InvoiceID = #InvoiceID
if exists (select 1 from #temp)
begin
select InvoiceID, ItemID, Price from #temp
RAISERROR ('There are non priced items. Invoice can not be approved!',16, 1)
return
end
update #InvoiceDocument
set DocStatus = 1
where InvoiceID = #InvoiceID
end
when I execute this:
sp_ApproveInvoice 1
It both generates a resultset (Results), and an error message (Messages).
On the vb6 side, vb6 can not catch the error here.
You need to RAISERROR in the stored proc to set the error.
Or use output parameters. Or the RETURN statement
Or add logic to distinguish "success" and "fail" recordsets in the client code.
Currently, Err has no meaning because there is no error state
Personally, I use RAISERROR as per my question here with this template
Your particular case cannot be caught with Err since your stored procedure is not generating any error in the traditional sense---it's either giving an empty result set or a normal result set. How can VB know the semantics of a non-empty result set?
For this to work, you either need to change your stored procedure to raise error OR you need to check the result set returned by the stored procedure directly in your code (nothing to do with Err).

Resources