I have an OLE DB Command that originally contained a simple three line update:
update raw_CommissionPaid set PolicyNumber = ? where PolicyNumber = ?
and this worked fine, did exactly as expected and all was good.
I recently decided though that this has the potential to go wrong and do a lot of damage so i decided i would put it all in a transaction, monitor the rows affected and roll back if it changed more than what i was expecting, so i changed my update to this:
BEGIN TRY BEGIN TRAN UpdateCommissionsPaidPolNo
update raw_CommissionPaid set PolicyNumber = ? where PolicyNumber = ?
IF ##ROWCOUNT <> 1 RAISERROR('Row count <> 1', 11, 1)
COMMIT TRAN END TRY BEGIN CATCH
ROLLBACK TRAN UpdateCommissionsPaidPolNo
PRINT 'UpdateCommissionsPaidPolicyNumber script failed'
SELECT
ERROR_MESSAGE() as ErrorMessage END CATCH
However this gave me the "Syntax error or general error" message that it usually gives. I remembered from a previous issue that sometimes it cant map parameters to the ?'s if they are embedded within other SQL, i thought that might be the issue so changed it to this just incase:
Declare #FNumber varchar(20) declare #LNumber varchar(20)
set #FNumber = ? set #LNumber = ?
BEGIN TRY BEGIN TRAN UpdateCommissionsPaidPolNo
update raw_CommissionPaid set PolicyNumber = #FNumber where
PolicyNumber = #LNumber
IF ##ROWCOUNT <> 1 RAISERROR('Row count <> 1', 11, 1)
COMMIT TRAN END TRY BEGIN CATCH
ROLLBACK TRAN UpdateCommissionsPaidPolNo
PRINT 'UpdateCommissionsPaidPolicyNumber script failed'
SELECT
ERROR_MESSAGE() as ErrorMessage END CATCH
but from this i still get :
Syntax Error, permission violation or other non specific error
i realised it might be because of the print, or the returning of the error message, but removing them doesnt change anything, it still fails
im sure the sql is valid as i tested it in SQL server management studio.
Anyone faced this? Is there a setting im supposed to change to allow this kind of change?
Thanks in advance
After reading This site i learned that if i set the data flow tasks TransactionOption property to supported then if it fails it supposedly rolls the failing section back itself.
Armed with this knowledge i figured then the only thing i needed to do was make it realise that multiple rows being updated counted as an error. So changing it to just this:
update raw_CommissionPaid set PolicyNumber = ? where PolicyNumber = ?
IF ##ROWCOUNT <> 1 RAISERROR('Row count <> 1', 11, 1)
should make it roll itself back.
Related
I'm creating a procedure to delete a row from my plants Table. The try and catch statement was to delete it but if the plantID doesn't match any plant, catch the error and print the error message.
However, the catch isn't working and I'm unsure what how to fix it.
CREATE PROC spDeletePlant
#PlantID INT,
#PlantCode INT,
#PlantName VARCHAR(25),
#Description VARCHAR(50),
#ListPrice MONEY,
#BatchID INT,
#CategoryID INT
AS
BEGIN TRY
DELETE FROM Plants
WHERE PlantID = #PlantID
END TRY
BEGIN CATCH
THROW 50001, 'PlantID is incorrect.', 1
END CATCH;
Forgive the formatting, I am new to this site.
A query not matching any rows is not an error - when you run the delete statement on its own, you don't see an error raised.
If you want to automatically throw an error if no rows are deleted you can simply do:
DELETE FROM Plants
WHERE PlantID = #PlantID
IF ##Rowcount = 0
BEGIN
THROW 50001, 'PlantID is incorrect.', 1
END
Your code is working but is not doing what you expect. This is because delete statement doesn't not throw any error in case row is not found.
What you can do is :
Do a previous select to look for the register being deleted, otherwise throw an error. Look #Stu answer..
Check the affected rows variable. Which DB are you using? In MSSQL for example you can check the ##ROWCOUNT
PD:
Are you sure you want to throw an exception when a row is not found?
Usually Exceptions are reserved for an exceptional things such as I/O errors, bad params etc..
Additionally, you should take in a count that Exceptions costly so is not a good way of returning data.
I think you should check the number of affected rows.
I created the below sp. When I execute it and insert an ID that does not exist, I want an error message to appear. However, the error message does not get printed... Does anyone can help me understand what I'm doing wrong please? Thank you
create procedure campaign_data
#campaign_ID bigint
as
begin
select campaignname,totalspend,clicks,impressions,totalspend/clicks as cpc
from DSP_RawData_Archive
where #campaign_ID=Campaign_ID
end
exec campaign_data 2
if ##ROWCOUNT=0 print 'Campaign_ID does not exist'
The PRINT statement will be displayed under the Messages tab on the results window if you are using SSMS and doesn't affect control flow.
Try throwing an error using RAISERROR with high enough severity (the middle parameter), which can affect control flow (jumps to CATCH or stops execution, for example).
IF ##ROWCOUNT = 0
RAISERROR('Campaign_ID does not exist', 15, 1)
The problem here is the scope of your ##ROWCOUNT. ##ROWCOUNT returns the number of effected rows of the last executed statement in a batch, in this case that from exec campain_data 2 and not that from the first select. Another example:
BEGIN TRAN
SELECT 0 WHERE 1 = 0
PRINT ##ROWCOUNT -- Displays 0
EXEC dbo.DoSomething -- Say this procedure returns 2 rows...
PRINT ##ROWCOUNT -- Displays 2
COMMIT
Another thing here is that you maybe want to show a proper error message in your scenario (instead of a simple PRINTed line). You can do achieve this using either
RAISERROR('Display my custom error message via RAISERROR!',16,1)
or
THROW 50000,'Display my custom error message via THROW!',1
Helpful article:
http://sqlhints.com/2013/06/30/differences-between-raiserror-and-throw-in-sql-server/
Try :
create procedure campaign_data
#campaign_ID bigint
as
begin
select campaignname,totalspend,clicks,impressions,totalspend/clicks as cpc
from DSP_RawData_Archive
where #campaign_ID=Campaign_ID
end
exec campaign_data 2
IF (SELECT ##rowcount) = 0
SELECT 'Campaign_ID does not exist'
I tried your stored procedure in Sequel Server Management Studio, and I saw the right response in message tab.
Where are you trying it?
I have a long running script that I want to wrap in transactions which could have up to 100+ transactions in it.
I would like to implement some error handling and re-use of code. I was thinking of using a label and GOTO statements.
BEGIN TRY
BEGIN TRANSACTION abc
--DO SOMETHING
COMMIT TRANSACTION abc;
END TRY
BEGIN CATCH
GOTO ERROR_OUT
END CATCH
ERROR_OUT:
SELECT ERROR_MESSAGE() [ErrorMessage];
ROLLBACK;
Is this something that should be done while using many transactions?
In my simple tests where I forced an error I noticed that the ERROR_MESSGE() SELECT statement did not return results.
Is there a way to get that to select statement to return results when using a label?
Declare #Table table (Num float,Den float,Pct float)
Insert into #Table values (25,100,0),(50,0,0)
BEGIN TRY
BEGIN TRANSACTION abc
-- Force a Divide by Zero on 2nd record
Update #Table Set Pct=Num/Den
COMMIT TRANSACTION abc;
Select * from #Table
GOTO CLEAN_OUT
END TRY
BEGIN CATCH
Declare #Error varchar(max)=Error_Message()
GOTO ERROR_OUT
END CATCH
ERROR_OUT:
ROLLBACK;
Select ErrorMessage=#Error
Select * From #Table
CLEAN_OUT:
Go
Returns
ErrorMessage
Divide by zero error encountered.
Num Den Pct
25 100 0
50 0 0
And then try it with (50,125,0)
In general, programmers are discouraged from using goto. The reason is that it is a control-flow primitive, and alternatives exist for many commonly used cases: if/then/else, while, and so on.
However, there are circumstances where goto is quite useful. And error handling is one of them. If you google "goto error handling", it is not hard to find explanations such as this, or this (although for C, I think the reasoning is very similar).
I have often used goto in SQL Server stored procedures for exactly this purpose. Although in my case, the goal was to achieve proper auditing and error processing when leaving the stored procedure.
Your use-case would seem to be a fine example where goto is a very reasonable way to code the block. I do find the "hundreds of transactions" part more questionable. Personally, I like to be explicit about when and where transactions are processed, so I would be more likely to write:
. . .
begin catch:
rollback ;
goto error_out;
end;
That is, to explicitly rollback the transaction "next to" where the transaction begins, rather than doing that in some far-away code block.
I am coding SQL Server 2005 trigger. I want to make some logging during trigger execution, using INSERT statement into my log table. When there occurs error during execution, I want to raise error and cancel action that cause trigger execution, but not to lose log records. What is the best way to achieve this?
Now my trigger logs everything except situation when there is error - because of ROLLBACK. RAISERROR statement is needed in order to inform calling program about error.
Now, my error handling code looks like:
if (#err = 1)
begin
INSERT INTO dbo.log(date, entry) SELECT getdate(), 'ERROR: ' + out from #output
RAISERROR (#msg, 16, 1)
rollback transaction
return
end
Another possible option is to use a table variable to capture the info you want to store in your permanent log table. Table variables are not rolled back if a ROLLBACK TRANSACTION command is given. Sample code is below...
--- Declare table variable
DECLARE #ErrorTable TABLE
( [DATE] smalldatetime,
[ENTRY] varchar(64) )
DECLARE #nErrorVar int
--- Open Transaction
BEGIN TRANSACTION
--- Pretend to cause an error and catch the error code
SET #nErrorVar = 1 --- ##ERROR
IF (#nErrorVar = 1)
BEGIN
--- Insert error info table variable
INSERT INTO #ErrorTable
( [Date], [Entry] )
SELECT
getdate(), 'Error Message Goes Here'
RAISERROR('Error Message Goes Here', 16, 1)
ROLLBACK TRANSACTION
--- Change this to actually insert into your permanent log table
SELECT *
FROM #ErrorTable
END
IF ##TRANCOUNT 0
PRINT 'Open Transactions Exist'
ELSE
PRINT 'No Open Transactions'
The problem here is that logging is part of transaction that modifies your data. Nested transactions will not help here. What you need is to put you logging actions into a separate context (connection), i.e. make it independent from you current transaction.
Two options come to my mind:
use Service Broker for logging - put log data to queue, receive and save the data 'on the other side of the pipe' (i.e. in another process/connection/transaction)
use OPENQUERY - you will need to register you own server as a 'linked server' and execute queries 'remotely' (I know, this looks a little bit strange, but an option anyway ...)
HTH
Don't know if I'm thinking too simple, but why not just change the order of the error handler to insert AFTER the rollback??
if (#err = 1)
begin
RAISERROR (#msg, 16, 1)
rollback transaction
INSERT INTO dbo.log(date, entry) SELECT getdate(), 'ERROR: ' + out from #output
return
end
Checkout error handling in triggers.
(SQL 2005)
Is it possible for a raiserror to terminate a stored proc.
For example, in a large system we've got a value that wasn't expected being entered into a specific column. In an update trigger if you write:
if exists (select * from inserted where testcol = 7)
begin
raiseerror('My Custom Error', 16, 1)
end
the update information is still applied.
however if you run
if exists (select * from inserted where testcol = 7)
begin
select 1/0
end
a divide by 0 error is thrown that actually terminates the update.
is there any way i can do this with a raiseerror so i can get custom error messages back?
In a trigger, issue a ROLLBACK, RAISERROR and then RETURN.
see Error Handling in SQL Server - Trigger Context by Erland Sommarskog
Can you not just add a CHECK constraint to the column to prevent it from being inserted in the first place?
ALTER TABLE YourTable ADD CONSTRAINT CK_No_Nasties
CHECK (testcol <> 7)
Alternatively you could start a transaction in your insert sproc (if you have one) and roll it back if an error occurs. This can be implemented with TRY, CATCH in SQL Server 2005 and avoids having to use a trigger.
Begin try
#temp number
#temp=1/0
End try
Begin catch
#errormsg varchar(100)
#errormsg=error_massage()
Raiseerror(#errormsg,16,1)
End catch
You should check for valid data prior to performing the update.
IF (#testvalue = 7)
RAISERROR("Invalid value.", 16, 1);
ELSE
UPDATE...