SQL server - stored procedure - ignore exception - sql-server

I have inside a stored procedure:
INSERT INTO SitesCategories(SiteID, CategoryID) VALUES(#SITEID, #TempCId);
This insert could throw exceptions because I have this constraint on the table:
ALTER TABLE dbo.SitesCategories
ADD CONSTRAINT UniqueSiteCategPair
UNIQUE (SiteId,CategoryID);
I made this constraint so that I would not have to check when inserting the uniques of the pairs (#SITEID, #TempCId). But I do not want that an SQl exception is thrown when this gets executed inside the stored procedure. How can I "catch" the exception inside the stored procedure and continue operations inside the procedure ?

BEGIN TRY
INSERT INTO SitesCategories(SiteID, CategoryID) VALUES(#SITEID, #TempCId);
END TRY
BEGIN CATCH
END CATCH;

If you create your UNIQUE CONSTRAINT with the IGNORE_DUP_KEY = ON setting, then duplicates will be silently ignored, e.g. they won't be inserted into your table, but no exception will be thrown, either:
ALTER TABLE dbo.SitesCategories
ADD CONSTRAINT UniqueSiteCategPair
UNIQUE (SiteId,CategoryID) WITH (IGNORE_DUP_KEY = ON);

Related

How to skip data which violates foreign key constraint and insert the remaining data in SQL Server? How to handle specific error in SQL Server?

I am using a spring boot program to load some data to a child table in MS SQL server. I am getting the data from a service and looping the insert statement for each item. There is some corrupted data which violates foreign key constraints. I want to skip the data which violates FK constraints
and continue inserting remaining data.
I am using the below insert query to do the same
begin try INSERT INTO [dbo].[TABLENAME](.....column names......)VALUES(.....) end try begin catch end catch;
The try catch is handling the FK constraint exception that is thrown and letting the program continue to execute other records in the loop without any issue but it is also not throwing error for any other constraint violation. Is there a way to specifically mention error type in the try catch so it handles only FK constraint.
I was able to do similar constraint specific catch in Oracle using exception when dup_val_on_index then in other program. Any help is greatly appreciated!
You can use ##ERROR, ERROR_NUMBER() or ERROR_MESSAGE() within begin catch end catch block.
Example:-
Begin try
INSERT INTO [dbo].[TABLENAME](.....column names......)VALUES(.....)
End try
Begin catch
IF ERROR_NUMBER() != 547
-- IF ##ERROR != 547
-- IF ERROR_MESSAGE() Not like '%The INSERT statement conflicted with the FOREIGN KEY constraint%'
Throw
End catch

How do I prevent a procedure from executing when an invalid input value is entered?

I have created two tables: workouts.session and workouts.exercises.
I have created a stored procedure to enter workout sessions into the log. However, I could not figure out how to prevent a user from entering invalid or incomplete data. How should I change my code? For instance, I need SQL to throw an error when a user enters invalid exercise id, or when all entries are null.
Tried different things, but everything failed. Believe I must add a transaction statement, and raiseerror statement. Tried different things, but I do not have enough experience with this :(
Original code:
CREATE PROCEDURE Workouts.AddSession
#SessionDate datetime,
#Exercise_ID int,
#Weight_Time int,
#Unit char(7),
#Reps int,
#Set int,
#RestSeconds int,
#Comment char(256)
AS
BEGIN
INSERT INTO workouts.Session (SessionDate, Exercise_ID, Weight_Time,
Unit, Reps, "Set", Comment)
VALUES (#SessionDate, #Exercise_ID, #Weight_Time,
#Unit, #Reps, #Set, #RestSeconds, #Comment)
END
To answer as close as possible to your question, I recommend try-transaction block with raiserror:
CREATE PROCEDURE Workouts.AddSession
#SessionDate datetime,
#Exercise_ID int,
#Weight_Time int,
#Unit char(7),
#Reps int,
#Set int,
#RestSeconds int,
#Comment char(256)
AS
BEGIN
set xact_abort on
begin try
begin transaction
-- start business here
if #Excercise_ID not in (5,7,8 /*...valid ids here....*/)
raiserror('Exercise_ID %i is invalid',16,0,#Exercise_ID)
if #Weight_Time is null and #Unit is null and #Reps is null and......
raiserror('All gym parameters are null',16,0)
INSERT INTO workouts.Session (SessionDate, Exercise_ID, Weight_Time,
Unit, Reps, "Set", Comment)
VALUES (#SessionDate, #Exercise_ID, #Weight_Time,
#Unit, #Reps, #Set, #RestSeconds, #Comment)
-- end business here
commit
end try
begin catch
if ##trancount > 0 rollback
declare #rethrow nvarchar(4000)='Error >> num '+convert(nvarchar(max),error_number())+', line '+convert(nvarchar(max),error_line())+', message: '+error_message()
raiserror(#rethrow, 16, 0)
end catch
However, you have to think if the stuff of your question apply not only to this procedure, but to the table workouts.Session regardless of source. In that case, in order to ensure data integrity, you should use constraints instead.
Making sure no set of columns are all-null can be done with a CHECK CONSTRAINT:
alter table workouts.Session add constraint CHK__WORKOUTS_SESSION__NOT_ALL_NULL CHECK(not (Weight_Time is NULL asnd Unit is NULL and Reps is NULL and ....))
CHK__WORKOUTS_SESSION__NOT_ALL_NULL is just the constraint's name, use whatever you want. Using a prefix like CHK is a good idea
About having valid Exercise_ID, you can do this with a check constraint too. Same syntax as above, but instead CHECK(Exercise_ID in (5,6,7....))
Notice that, unlike the same check in the procedure, in a check constraint you can not use another table for the IN clause. The best way for valid values is to have a master table with Exercise_ID as primary key, have all possible values there, then add a FOREIGN KEY there:
alter table workouts.Session add constraint FK__WORKOUTS_SESSION__EXERCISE_ID__REF__OTHERTABLE__ID
foreign key(Exercise_ID) references othertable(id)

is too long. Maximum length is 128. while using double quotes

I have a SP in MSSQL that will insert and error log to the SQL
ALTER PROCEDURE [dbo].[TestLog]
#PrimaryName nvarchar(255),
#ERROR nvarchar(4000)
AS
BEGIN
insert into Process_Log
(
PrimaryName ,
ERROR
) VALUES
(
#PrimaryName ,
#ERROR
)
END
My sample error message is this:
Violation of PRIMARY KEY constraint 'PK__AP__2EC21549E681BC94'. Cannot insert duplicate key in object 'dbo.Test'. The duplicate key value is (215009).
The statement has been terminated.
as I have the ' in string I have to use double quotes and SET QUOTED_IDENTIFIER OFF.
I am unable to change the error message or edit it as it is from other application.
EDIT:
My Process table structure:
CREATE TABLE [dbo].[Process_Log](
[PrimaryName] [nvarchar](255) NULL,
[ERROR] [nvarchar](max) NULL
)
In your query replace:
insert into Process_Log
(
PrimaryName ,
ERROR
) VALUES
(
#PrimaryName ,
#ERROR
)
with:
insert into Process_Log
(
PrimaryName ,
ERROR
) VALUES
(
#PrimaryName ,
REPLACE(#ERROR, '''', '''''')
)
This is NOT changing the error message, just "escaping" the single quotes so that SQL Server won't get confused.
You will also need to get rid of the QUOTED_IDENTIFIER bit and forget about double quotes.
I'm starting to think that this isn't SQL Server, as that "stuff" you added in your comment below certainly isn't SQL as I know it... and it doesn't do anything if I try running it through SSMS.
Try this:
DECLARE #test TABLE (error VARCHAR(MAX));
--Doesn't work due to single quotes
--INSERT INTO #test SELECT 'Violation of PRIMARY KEY constraint 'PK__AP__2EC21549E681BC94'. Cannot insert duplicate key in object 'dbo.Test'. The duplicate key value is (215009). The statement has been terminated.';
INSERT INTO #test SELECT 'Violation of PRIMARY KEY constraint ''PK__AP__2EC21549E681BC94''. Cannot insert duplicate key in object ''dbo.Test''. The duplicate key value is (215009). The statement has been terminated.';
SELECT * FROM #test;
If the column does not support strings with, then you will need to alter your TestLog table and increase the size of the column which was truncated. You can learn how to alter a table in MS SQL here:
https://learn.microsoft.com/en-us/sql/t-sql/statements/alter-table-transact-sql?view=sql-server-2017
You will need to change your parameter from
#ERROR nvarchar(4000)
to
#ERROR nvarchar(MAX)
Also, you will need to take a look at how many characters your error message is and act accordingly.
And also, you will need to make sure your quotes are properly escaped.
I have found a solution for that: I have to execute the SP with SET QUOTED_IDENTIFIER OFF before exec SP, I had SET QUOTED_IDENTIFIER OFF - in my SP - but it has to be executed every time I execute SP. (SP is executed in the software.)
Example:
SET QUOTED_IDENTIFIER OFF
Exec [TestLog]
#PrimaryName = "test"
,#ERROR = "Violation of PRIMARY KEY constraint 'PK__AP__2EC21549E681BC94'. Cannot insert duplicate key in object 'dbo.Test'. The duplicate key value is (215009).
The statement has been terminated."
Then I will avoid a a 128 len probmem.

SQL Server 'Invalid column name' in transaction

I have a script where I'm adding a column to the table, and immediately after I populate that column with data from another table. I'm getting 'Invalid column name' error on the column that I am adding.
The error, specifically, is Invalid column name 'tagID'.
The code between BEGIN TRANSACTION and COMMIT is actually an excerpt of a much larger script, but this is the relevant excerpt (and I need all of it to succeed or simply roll back):
BEGIN TRY
BEGIN TRANSACTION
ALTER TABLE [Items] ADD tagID [uniqueidentifier] NULL
MERGE INTO
Items AS target
USING
Tags AS t ON t.tag = target.tag
WHEN MATCHED THEN
UPDATE SET target.tagID = t.id;
COMMIT
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
END CATCH
GO
SQL Server tries to compile all statements in the batch. If the table doesn't exist compilation of the statement is deferred but there is no deferred compilation for missing columns.
You can use
BEGIN TRY
BEGIN TRANSACTION
ALTER TABLE [Items] ADD tagID [uniqueidentifier] NULL
EXEC('
MERGE INTO
Items AS target
USING
Tags AS t ON t.tag = target.tag
WHEN MATCHED THEN
UPDATE SET target.tagID = t.id;
')
COMMIT
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
END CATCH
GO
To push the usage of the column into a child batch compiled after the column is created. It still belongs to the same transaction opened in the parent scope.
Transaction scope are for DML operations and not for DDL operations. So not sure why you are having the ALTER statement in the same transaction block. If not wrong, you should be having that ALTER statement outside the transaction block.
ALTER TABLE [Items] ADD tagID [uniqueidentifier] NULL
BEGIN TRANSACTION
MERGE INTO
.....
Also, I would remove those [] from the datatype of the column from your ALTER statement
ALTER TABLE [Items] ADD tagID UniqueIdentifier;

SQL Server error creating a foreign key constraint

I am trying to re-create a foreign key constraint that got deleted recently and SQL Server is not letting me. Here's the DDL that SQL Server Management Studio gave me:
/* To prevent any potential data loss issues, you should review this script in detail before running it outside the context of the database designer.*/
BEGIN TRANSACTION
SET QUOTED_IDENTIFIER ON
SET ARITHABORT ON
SET NUMERIC_ROUNDABORT OFF
SET CONCAT_NULL_YIELDS_NULL ON
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
COMMIT
BEGIN TRANSACTION
GO
ALTER TABLE dbo.CleansingOperations SET (LOCK_ESCALATION = TABLE)
GO
COMMIT
BEGIN TRANSACTION
GO
ALTER TABLE dbo.CleansedData ADD CONSTRAINT
FK_CleansedData_CleansingOperations FOREIGN KEY
(
CleansedOperationID
) REFERENCES dbo.CleansingOperations
(
CleansingOperationID
) ON UPDATE NO ACTION
ON DELETE NO ACTION
GO
ALTER TABLE dbo.CleansedData SET (LOCK_ESCALATION = TABLE)
GO
COMMIT
and here is the error that I get when I run it:
Msg 547, Level 16, State 0, Line 1
The ALTER TABLE statement conflicted with the FOREIGN KEY constraint "FK_CleansedData_CleansingOperations". The conflict occurred in database "NetVis203", table "dbo.CleansingOperations", column 'CleansingOperationID'.
Msg 3902, Level 16, State 1, Line 1
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
What does it all mean? As far as I can see, it is saying that the constraint is 'conflicting' with itself!
It means that there's a row in CleansedData that doesn't have a corresponding row in CleansingOperations
SELECT CleansingOperationId FROM dbo.CleansedData
EXCEPT
SELECT CleansingOperationId FROM dbo.CleansingOperations
should return no rows for your statement to work.
There's a NOCHECK keyword, which gets around the error, but better to understand what rows would violate the foreign key.
The reason for the error message, is that SQL Server imagines the foreign key is in place and then asserts that all rows satisfy the constraint before committing the statement. The reason for the second error message has to do with error handling (which I can never get right either).

Resources