I am using SQL Server and trying to return an error from a stored procedure to a caller. However it does not seem to work. When debugging the returned value is null.
Error handling:
begin try
if not exists (select * from dbo.Employees where IsCEO = 1)
begin
insert into dbo.Employees (FirstName, LastName, Salary, IsCEO, IsManager, ManagerId, EmployeeRank)
values (#FirstName, #LastName, #Salary, #IsCEO, #IsManager, #ManagerId, #EmployeeRank)
end
end try
begin catch
return raiserror ('CEO already exists', 15, 1)
end catch
To repeat what I state in the comments, firstly your RETURN in the CATCH doesn't make sense. You don't RETURN an error, RETURN provides back an int value, and (historically) is used to denote the success of the procedure (0 meaning success, anything else meaning failure). Their use, however, is somewhat more historical, especially now with things like THROW and OUTPUT parameters.
Speaking of THROW, you really should be using that too. As the documentation on RAISERROR states:
Note
The RAISERROR statement does not honor SET XACT_ABORT. New applications should use THROW instead of RAISERROR.
Finally, your error and your IF conflict. Your IF checks to see if a CEO does not exist, however, the error you raise states that they do.
I suspect therefore, you likely want something like like this, which doesn't need the TRY...CATCH, and just an IF...ELSE:
BEGIN
IF NOT EXISTS (SELECT * FROM dbo.Employees WHERE IsCEO = 1)
INSERT INTO dbo.Employees (FirstName,
LastName,
Salary,
IsCEO,
IsManager,
ManagerId,
EmployeeRank)
VALUES (#FirstName, #LastName, #Salary, #IsCEO, #IsManager, #ManagerId, #EmployeeRank);
ELSE
THROW 50001, N'CEO does not exist', 16; --Use an error number (and state) appropriate for your environment
--This will only be reached if the ELSE was not entered
RETURN 0;
END;
Your issue here is that you never encounter error in this code.
Logic of your code says if CEO doesn't exist, insert employee in the table. If it exists, do nothing. And then you check for errors in this and say CEO already exists which makes no sense.
Seem to me you wanted to do follwing.
begin try
if not exists (select * from dbo.Employees where IsCEO = 1)
begin
insert into dbo.Employees (FirstName, LastName, Salary, IsCEO, IsManager, ManagerId,
EmployeeRank)
values (#FirstName, #LastName, #Salary, #IsCEO, #IsManager, #ManagerId,
#EmployeeRank)
end
ELSE
BEGIN
raiserror ('CEO already exists', 15, 1)
END
end try
begin catch
--some code that handles possible errors in code above
end catch
Related
I would need to help to understand why the CATCH is not catching the error.
SCENARIO 1 - working OK.
BEGIN TRY
if object_id('tempdb..#temp') is not NULL drop table #temp
create table #temp (name varchar(1))
insert into #temp
values
('AA')
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() AS ErrorNumber;
END CATCH
ERROR_MESSAGE: String or binary data would be truncated.
SCENARIO 2
(where I forget to put the alias for the column)
BEGIN TRY
if object_id('tempdb..#temp') is not NULL drop table #temp
create table #temp (name varchar(1))
if object_id('tempdb..#temp01') is not NULL drop table #temp01
create table #temp01 (name varchar(1))
insert into #temp
values ('A')
insert into #temp01
values ('A')
select name
from #temp a
join #temp01 b on a.name = b.name
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() AS ErrorNumber;
END CATCH
In this case the error is not caught.
Can someone please explain why?
Thanks
TRY/CATCH is a runtime construct. In order to get to CATCH, your code has to run. Your code can’t run if it has a parse or compilation error. An even simpler example is the following:
BEGIN TRY;
SELECT; -- or SELECT foo; or any invalid syntax
END TRY
BEGIN CATCH;
PRINT 1;
END CATCH;
CATCH never happens here because SELECT; is invalid T-SQL and fails parsing, very much like your ambiguous column fails parsing, preventing CATCH from happening because the code never runs:
Msg 102, Level 15, State 1, Line 2
Incorrect syntax near ';'.
We can look at it the other way around too, so we don't get too hung up on CATCH "failing" to capture compilation errors. What if the
BEGIN TRY;
SELECT 1;
END TRY
BEGIN CATCH;
PRINT; -- invalid syntax!
END CATCH;
Msg 102, Level 15, State 1, Line 5
Incorrect syntax near ';'.
Note the line number: the syntax error here is inside CATCH, which shouldn't be reached, given that everything in TRY should work just fine. However, as with the previous example, the code inside both TRY and CATCH has to compile before it can run. If it can't compile, it's going to fail before TRY or CATCH ever become a thing.
I have a procedure which is supposed to save data into a temporary table and at the end of it delete the old records and insert the new ones into the real table.
The end of the procedure is as follows:
...
--THIS SELECT HAS THE CORRECT DATA
SELECT * FROM #NewTempTable;
BEGIN TRY
BEGIN TRANSACTION;
--DELETE OLD COMPONENTS
DELETE FROM AlteracaoEngenharia.hComponents
WHERE Id IN (SELECT C.Id
FROM AlteracaoEngenharia.hComponents AS C
JOIN AlteracaoEngenharia.hEngineeringChanges AS EC ON C.EngineeringChangeId = EC.Id
WHERE C.Name NOT LIKE '%(Manual)%' AND EC.State LIKE 'Aberto');
INSERT INTO AlteracaoEngenharia.hComponents (Name, OldQuantity, Quantity, CDP, Value, Type, EngineeringChangeId)
(SELECT
Name, OldQuantity, Quantity, CDP, Value, Type, EngineeringChangeId
FROM
#NewTempTable);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
END CATCH
END
If I select all the procedure until the begin try and f5(execute it) it it gives no errors and I get the expected data on #NewTempTable; But when save the procedures and try to Execute it I get the following error
While #NewTempTable has the expected data, the execution of the procedure returns -6 and The data doesn't get inserted into AlteracaoEngenharia.hComponents.
None of the data I receive is null, neither are the AlteracaoEngenharia.hComponents columns not nullable.
I also have the same procedure in a TestServer exactly equal (some connections are different, but if that would be the mistake I would get no data) and that procedure works fine.
I know I'm not showing most of the procedure, buts it's a bit complex and with sensible information. I'm hoping I can get some pointers on the right direction with what I have described.
PS: I have tried with both #newTempTable and ##newTempTable
Try below code bock:
BEGIN TRANSACTION;
BEGIN TRY
--DELETE OLD COMPONENTS
Delete From AlteracaoEngenharia.hComponents where Id in
(Select C.Id From AlteracaoEngenharia.hComponents as C
Join AlteracaoEngenharia.hEngineeringChanges as EC on C.EngineeringChangeId = EC.Id
Where C.Name not like '%(Manual)%' and EC.State like 'Aberto');
Insert Into AlteracaoEngenharia.hComponents (Name, OldQuantity, Quantity, CDP, Value, Type, EngineeringChangeId)
(Select Name, OldQuantity, Quantity, CDP, Value, Type, EngineeringChangeId From #NewTempTable);
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION;
throw;
END
END CATCH;
IF ##TRANCOUNT > 0
BEGIN
COMMIT TRANSACTION;
PRINT 'Successfully Completed'
END
The best way to ensure a full ROLLBACK after an error is to use XACT_ABORT, this forces all errors to abort the batch, and rollback the transaction:
SET XACT_ABORT ON;
SET NOCOUNT ON;
BEGIN TRANSACTION;
--DELETE OLD COMPONENTS
DELETE FROM AlteracaoEngenharia.hComponents
WHERE Id IN (SELECT C.Id
FROM AlteracaoEngenharia.hComponents AS C
JOIN AlteracaoEngenharia.hEngineeringChanges AS EC ON C.EngineeringChangeId = EC.Id
WHERE C.Name NOT LIKE '%(Manual)%' AND EC.State LIKE 'Aberto');
INSERT INTO AlteracaoEngenharia.hComponents (Name, OldQuantity, Quantity, CDP, Value, Type, EngineeringChangeId)
(SELECT
Name, OldQuantity, Quantity, CDP, Value, Type, EngineeringChangeId
FROM
#NewTempTable);
COMMIT TRANSACTION;
No TRY/CATCH or ROLLBACK is necessary here, any error automatically rolls back.
I don't know how to use if else in this case. When score > 10, stop insert. Else continue insert as normally. But what is the syntax to do that?
CREATE TRIGGER invalidScore ON dbo.dbo_score
AFTER INSERT
AS
DECLARE #score DECIMAL;
SET #score = (SELECT s.score FROM Inserted s);
IF(#score > 10)
BEGIN
RETURN 'score must be less than 10'
ROLLBACK TRAN
END
ELSE
BEGIN
END
There are 3 things you need to change for this trigger to work:
Remove the else section - its optional.
Handle the fact that Inserted may have multiple rows.
Throw the error rather than using the return statement so you can handle it in the client. And throw it after rolling back the transaction in progress.
Corrected trigger follows:
create trigger invalidScore on dbo.dbo_score
after insert
as
begin
if exists (select 1 from Inserted S where S.Score > 10) begin
rollback tran;
throw 51000, 'score must be less than 10', 1;
end
end
First, creating these types of sql objects should use begin.. end blocks. Second is,you can ignore the else statement.
CREATE TRIGGER invalidScore ON dbo.dbo_score
AFTER INSERT
AS
BEGIN
DECLARE #score DECIMAL;
SET #score = (SELECT s.score FROM Inserted s);
IF(#score > 10)
BEGIN
RETURN 'score must be less than 10'
ROLLBACK TRAN
END
END
'Else' is an option section you can remove this and use it,but i may like you to consider using check constraints for scenarios like this rather than adding a trigger check on score column
e.g.
CREATE TABLE dbo.dbo_score(
Score int CHECK (score < 10)
);
A CHECK constraint is faster, simpler, more portable, needs less code and is less error prone
EDIT3: SOLUTION
Apparently my code had problems with the BEGIN / END blocks, as the users that replied suggested (thank you!)
Still, I couldn't get it you work, until I found this post and tried this if/else structure: using Switch like logic in T-SQL
EDIT2:
I've written a couple PRINTs in the procedure and the values are passing just right. Now it only says 'Invalid ID', even while sending 200.
220
dsf4
Oct 12 2018 10:10AM
Msg 50000, Level 16, State 1, Procedure p_ValCulture, Line 188
Invalid ID
EDIT:
I've tried setting the error message inside BEGIN / END; it didn't work. This is the code for that part.
IF #operacion LIKE 'I'
BEGIN
IF #id IS NULL OR #id <= 0
BEGIN
SELECT #errorDesc = 'Invalid ID'
RAISERROR(#errorDesc, 16, 1)
END
IF EXISTS(SELECT 1 FROM Production.Culture AS pc WHERE PC.CultureID = #id)
BEGIN
SELECT #errorDesc = 'ID already exists'
RAISERROR(#errorDesc, 16, 1)
END
END
I get this if I execute the stored procedure
Msg 50000, Level 16, State 1, Procedure p_ValCulture4, Line 183
Below that message there is a blank space, the value of an error message that I couldn't set.
These are the parameters I pass:
GO
EXEC p_ValCulture4 200, 'John', '10-12-2018 10:10:10.000','I'
I'm using SQL Server Management 2014, on Windows 64bits.
I'm trying to catch up on some T-SQL I didn't get enough attention.. but I'm totally stuck :/ I've checked Google, Youtube, SO, etc.. and tried many things but nothing works and I'm wasting time totally stuck on this part that I can't understand.
I have a stored procedure that when sent an 'operation' as 'I' (char) will perform an insert into a table. I'm using the AdventureWorks2014 database as practise.
The problem is that I want to send different error messages if the id sent is null, or another one if it already exists in the table, etc. This is the code for the procedure:
CREATE PROCEDURE p_ValCulture4
#id INT,
#name NVARCHAR(50),
#date DATETIME,
#operacion CHAR(1)
AS
BEGIN
print #id
print #name
print #date
SET NOCOUNT ON
DECLARE #errorDesc nvarchar(MAX)
BEGIN TRY
SELECT #operacion = UPPER(#operacion)
IF #operacion = 'I' /* the Insert option */
BEGIN
IF #id IS NULL OR #id <= 0
BEGIN
SELECT #errorDesc = 'Invalid ID'
RAISERROR(#errorDesc, 16, 1)
END
IF EXISTS(SELECT 1 FROM Production.Culture AS pc WHERE PC.CultureID = #id)
BEGIN
SELECT #errorDesc = 'ID already exists'
RAISERROR(#errorDesc, 16, 1)
END
SELECT #errorDesc = 'ERROR: Insert error'
INSERT INTO Production.Culture VALUES
(#id, #name, #date);
SELECT 'Rows: ' + CONVERT(VARCHAR(10),##ROWCOUNT)
END
END TRY
BEGIN CATCH
RAISERROR (#errorDesc,16,1)
END CATCH
END
The first IF, if I send id = null works fine, I get the right error; but if I send an existing id, the IF get completely ignored. The same happens with the insert, it works fine, but only if there are no IFs in the procedure..
I can't get my mind how these IF - BEGIN / END work.. and why it can only read the first IF but ignores subsequent ones..
I've tried putting everything inside an IF - BEGIN / END and then ELSE - BEGIN / END, same results.
I've tried setting the error message inside de IFs, and also outside. Also, inside the IFs, but before BEGIN. I've tried writing the error directly inside RAISERROR, same results.
If anyone can help me understand why the IFs get ignored and the logic behind THESE IFs in T-SQL, it would be PRETTY much appreciated :) Thank you!
Consistent indenting will definitely help you see issues related to your Begin End pairs.
Your first IF test has the BEGIN on the same horizontal position below it, but the next IF test has its BEGIN indented further.
If you make this IF test like the first, in that the BEGIN & END sit on the same horizontal position, it will expose issues like ZLK points out in the above comment, SELECT #errorDesc = 'Invalid ID' is running outside the BEGIN END pair.
I am trying to make a bit of code that takes in 2 separate columns, a month and a year. From there I want it to see if those numbers entered have already passed or not. If they have passed, cause an error to pass and stop the transaction. Otherwise, I want it to continue on and insert new information into the table. I know I am close on getting this to work, but I cant seem to get the RAISERROR to fire. I am sure it has to do with the fact I am pretty new at this and I am missing some small detail.
Currently I am taking the two months in as variables and the making a third variable to use to turn the other two into a proper datetime format. Then I use the datediff function to try and see if it has passed that way. To no avail though. I keep getting the insert function going, even if the card date is old.
USE AdventureWorks2012
GO
CREATE TRIGGER BadCreditCardDate
ON Sales.CreditCard
INSTEAD OF INSERT
AS
Begin
DECLARE #ExpMonth tinyint,
#ExpYear smallint,
#ExpMonthYear datetime
SELECT #ExpMonth=ExpMonth,
#ExpYear=ExpYear,
#ExpMonthYear = #ExpYear + '-' + #ExpMonth + '-00'
FROM INSERTED
IF
DATEDIFF(MONTH,#ExpMonthYear,GETDATE()) < 0
BEGIN
RAISERROR ('The Credit Card you have entered has expired.' ,10,1)
ROLLBACK TRANSACTION
END
ELSE
Begin
INSERT INTO CreditCard (CardType, CardNumber, ExpMonth, ExpYear, ModifiedDate)
Select CardType, CardNumber, ExpMonth, ExpYear, ModifiedDate FROM inserted
END
End
I think there is a simpler way to check for expiration:
CREATE TRIGGER BadCreditCardDate
ON Sales.CreditCard
INSTEAD OF INSERT
AS
BEGIN
IF EXISTS (
SELECT 1
FROM inserted
WHERE (YEAR(GETDATE()) > ExpYear) OR (YEAR(GETDATE()) = ExpYear AND MONTH(GETDATE()) > ExpMonth)
)
BEGIN
RAISERROR ('The Credit Card you have entered has expired.' ,10,1)
ROLLBACK TRANSACTION
END
ELSE
BEGIN
INSERT INTO CreditCard (CardType, CardNumber, ExpMonth, ExpYear, ModifiedDate)
SELECT CardType, CardNumber, ExpMonth, ExpYear, ModifiedDate
FROM inserted
END
END
In this way you effectively check every record to be inserted in CreditCard.