SQL insert trigger to catch NULL values for mutiple rows - sql-server

I have written the trigger below that prevents from NULL being entered in the pch_x field . It works fine if i insert 1 row but doesnt work if I enter more than one at once . Could someone please help me out a little ? Here is my code
create trigger test
ON [dbo].TEMP
for INSERT
AS
BEGIN
declare #xcheck varchar(50)
set #xcheck= (select i.pch_x FROM temp L INNER JOIN INSERTED I
ON L.id = I.id)
F (#xcheck is NULL )
begin
RAISERROR('NULL in pch_x', 16, 1)
ROLLBACK
end
END

I'm not sure why you're doing this in a trigger, but the set based way to do this test would be to use EXISTS:
create trigger test
ON [dbo].TEMP
for INSERT
AS
BEGIN
IF EXISTS(select * FROM temp L INNER JOIN
INSERTED I
ON L.id = I.id
where i.pch_x IS NULL)
begin
RAISERROR('NULL in pch_x', 16, 1)
ROLLBACK
end
END
I'm also not sure why you're joining back to the table - I'd have thought the check could run without reference to temp:
create trigger test
ON [dbo].TEMP
for INSERT
AS
BEGIN
IF EXISTS(select * FROM INSERTED
where pch_x IS NULL)
begin
RAISERROR('NULL in pch_x', 16, 1)
ROLLBACK
end
END
For you unusual requirement that, in a rowset containing some rows with nulls, you want success for those rows without nulls and failure for those rows with nulls, most sensible would be an INSTEAD OF trigger:
create trigger test
ON [dbo].TEMP
INSTEAD OF INSERT
AS
BEGIN
declare #rc int
INSERT INTO dbo.temp (/* column list */)
SELECT /* column list */ from inserted where pch_x IS NOT NULL
set #rc = ##ROWCOUNT
IF #rc <> (select COUNT(*) from inserted)
begin
RAISERROR('NULL in pch_x', 16, 1)
--ROLLBACK
end
END

Related

T-SQL - Insert trigger; IF EXISTS not evaluating as intended

Not sure what I'm missing. When I debug and step through the INSERT query I've included below, I see that '%a%' is the value of #Answer, and 103 is the value for #ItemId.
IF EXISTS is always evaluating to false when I insert the values shown beneath:
CREATE TRIGGER TR_cc_Additional_Information_Answers_INS
ON cc_Additional_Information_Answers
AFTER INSERT
AS
BEGIN
CREATE TABLE temp_answers
(
TempAnswer VARCHAR(50),
TempAdditional_Information_ItemID INT
)
INSERT INTO temp_answers (TempAnswer, TempAdditional_Information_ItemID)
SELECT Description, Additional_Information_ItemID
FROM inserted
DECLARE #Answer varchar(50)
SELECT #Answer = '''%' + t.TempAnswer + '%''' FROM temp_answers t
DECLARE #ItemId int
SELECT #ItemId = t.TempAdditional_Information_ItemID FROM temp_answers t
IF EXISTS(SELECT 1
FROM cc_Additional_Information_Answers a
WHERE a.Description LIKE #Answer
AND a.Additional_Information_ItemID = #ItemId)
BEGIN
RAISERROR('Answer is too similar to pre-existing answers for this item', 16, 1)
ROLLBACK TRANSACTION
RETURN
END
DROP TABLE temp_answers
END
GO
And this is my insert query:
INSERT INTO cc_Additional_Information_Answers (Additional_Information_ItemID, Description)
VALUES (103, 'a')
And the pre-existing record:
Thanks in advance, SQL community!
EDIT: this also does not behave as expected. . .
INSERT INTO cc_Additional_Information_Answers (Additional_Information_ItemID, Description)
VALUES (103, 'a')
Given this data
Your IF EXISTS will always evaluate to true because the inserted value is already inserted (although it can be rolled back) when the trigger runs (it's an "AFTER" trigger).
So you will want to inspect only those records that existed in the table before the insertion. I always use an outer join for this. Also: I would never create a table in a trigger. The following should work as expected:
CREATE TRIGGER TR_cc_Additional_Information_Answers_INS ON cc_Additional_Information_Answers
AFTER INSERT
AS
BEGIN
IF EXISTS(
SELECT 1 FROM cc_Additional_Information_Answers a
LEFT OUTER JOIN inserted i ON a.Additional_Information_AnswerID = i.Additional_Information_AnswerID
INNER JOIN inserted temp ON a.Additional_Information_ItemID = temp.Additional_Information_ItemID
WHERE a.Description LIKE '%' + temp.Description + '%'
AND i.Additional_Information_AnswerID IS NULL
)
BEGIN
RAISERROR('Answer is too similar to pre-existing answers for this item', 16, 1)
ROLLBACK TRANSACTION
RETURN
END
END
GO

SQL Stored Procedure with Input parameters with While loop

I have a code below that should insert records into the table but unfortunately this code foes not work in case multiple records are inserted or updated or deleted. How should I rewrite the code for procedure to loop through all the inserted / deleted records? And I do need to use that stored procedure with Input parameters (not just simple insert into ... select ... from ...)
IF EXISTS (SELECT * FROM MyDB.sys.triggers WHERE object_id = OBJECT_ID(N'[dbo].[MyTable_DEL_UPD_INS]'))
DROP TRIGGER [dbo].[MyTable_DEL_UPD_INS]
GO
CREATE TRIGGER [dbo].[MyTable_DEL_UPD_INS]
ON [MyDB].[dbo].[MyTable]
AFTER DELETE, UPDATE, INSERT
NOT FOR REPLICATION
AS
BEGIN
DECLARE #PKId INT,
#Code VARCHAR(5),
#AuditType VARCHAR(10)
SET #Code = 'TEST'
IF EXISTS (SELECT * FROM deleted d)
AND NOT EXISTS (SELECT * FROM inserted i)
BEGIN
SELECT TOP 1
#PKId = d.[MyTable_PK],
#AuditType = 'DELETE'
FROM
deleted d WITH (NOLOCK)
IF #PKId IS NOT NULL
AND #Code IS NOT NULL
EXEC MyDB.[dbo].[SP_Audit] #PKId, #Code, #AuditType
END
IF EXISTS (SELECT * FROM deleted d)
AND EXISTS (SELECT * FROM inserted i)
BEGIN
SELECT TOP 1
#PKId = d.[MyTable_PK],
#AuditType = 'UPDATE'
FROM
deleted d WITH (NOLOCK)
IF #PKId IS NOT NULL
AND #Code IS NOT NULL
EXEC MyDB.[dbo].[SP_Audit] #PKId, #Code, #AuditType
END
IF NOT EXISTS (SELECT * FROM deleted d)
AND EXISTS (SELECT * FROM inserted i)
BEGIN
SELECT TOP 1
#PKId = d.[MyTable_PK],
#AuditType = 'INSERT'
FROM
deleted d WITH (NOLOCK)
IF #PKId IS NOT NULL
AND #Code IS NOT NULL
EXEC MyDB.[dbo].[SP_Audit] #PKId, #Code, #AuditType
END
END
GO
ALTER TABLE [MyDB].[dbo].[MyTable] ENABLE TRIGGER [MyTable_DEL_UPD_INS]
You should avoid using loops in triggers.
Triggers should be as quick to run as possible, since SQL Server will not return control to whatever statement that fired the trigger until the trigger is completed.
So instead of a loop, you should modify your SP_Audit procedure to work with multiple records instead of a single one.
usually, this is easily be done using a table valued parameter.
If you could post the SP_Audit as well, we could give you a complete solution.
Since you didn't post it, you can use these guidelines as a start:
First, you create a user defined table type:
CREATE TYPE dbo.Ids AS TABLE
(
Id int NOT NULL PRIMARY KEY
)
GO
Then, you create the procedure to use it:
CREATE PROCEDURE [dbo].[STP_Audit_MultipleRecords]
(
#IDs dbo.Ids readonly,
#Code CHAR(4),
#AuditType CHAR(6)
)
AS
-- Implementation here
GO
Last, your write your trigger like this:
CREATE TRIGGER [dbo].[MyTable_DEL_UPD_INS]
ON [MyDB].[dbo].[MyTable]
AFTER DELETE, UPDATE, INSERT
NOT FOR REPLICATION
AS
BEGIN
DECLARE #HasDeleted bit = 0,
#HasInserted bit = 0,
#AuditType CHAR(6),
#Code CHAR(4)
SET #Code = 'TEST'
DECLARE #IDs as dbo.Ids
IF EXISTS (SELECT * FROM deleted d)
SET #HasDeleted = 1
IF EXISTS (SELECT * FROM inserted i)
SET #HasInserted = 1
IF #HasDeleted = 1
BEGIN
IF #HasInserted = 1
BEGIN
SET #AuditType = 'UPDATE'
END
ELSE
BEGIN
SET #AuditType = 'DELETE'
END
END
ELSE
IF #HasInserted = 1
BEGIN
SET #AuditType = 'INSERT'
END
INSERT INTO #IDs (Id)
SELECT [MyTable_PK]
FROM inserted
UNION
SELECT [MyTable_PK]
FROM deleted
EXEC [dbo].[STP_Audit_MultipleRecords] #IDs, #Code, #AuditType
END
GO
Notes:
The #HasDeleted and #HasInserted variables are to allow you to only execute the EXISTS query once for every procedure.
Getting the primary key values from the deleted and inserted table is done using a single union query. Since union eliminates duplicate values, you can write this query just once. If you want to, you can write a different query for each audit type, but then you will have to repeat the same query 3 times (with different tables)
I've changed the data types of your #code and #AuditType variables to char, since they have a fixed length.

Using TRY / CATCH to perform INSERT / UPDATE

I have this pattern in a number of stored procedures
-- Table1
[id] [int] IDENTITY(1,1) NOT NULL
[data] [varchar](512) NULL
[count] INT NULL
-- 'data' is unique, with a unique index on 'data' in 'Table1'
BEGIN TRY
INSERT INTO Table1 (data, count) SELECT #data,1;
END TRY
BEGIN CATCH
UPDATE Table1 SET count = count + 1 WHERE data = #data;
END CATCH
I've been slammed before for using this pattern
You should never have exception "catching" in your normal logic flow. (Thus why it is called an "exception"..it should be exceptional (rare). Put a exists check around your INSERT. "if not exists (select null from Data where data = #data) begin /* insert here */ END
However, I can't see a way around it in this instance. Consider the following alternative approaches.
INSERT INTO Table1 (data,count)
SELECT #data,1 WHERE NOT EXISTS
(SELECT 1 FROM Table1 WHERE data = #data)
If I do this, it means every insert is unique, but I can't 'catch' an update condition.
DECLARE #id INT;
SET #id = (SELECT id FROM Table1 WHERE data = #data)
IF(#id IS NULL)
INSERT INTO Table1 (data, count) SELECT #data,1;
ELSE
UPDATE Table1 SET count = count + 1 WHERE data = #data;
If I do this, I have a race condition between the check and the insert, so I could have duplicates inserted.
BEGIN TRANSACTION
DECLARE #id INT;
SET #id = (SELECT id FROM Table1 WHERE data = #data)
IF(#id IS NULL)
INSERT INTO Table1 (data, count) SELECT #data,1;
ELSE
UPDATE Table1 SET count = count + 1 WHERE data = #data;
END TRANSACTION
If I wrap this in a TRANSACTION it adds more overhead. I know TRY/CATCH also brings overhead but I think TRANSACTION adds more - anyone know?.
People keep telling me that using TRY/CATCH in normal app logic is BAD, but won't tell me why
Note: I'm running SQL Server 2005 on at least one box, so I can't use MERGE
Try to update and if it's failed - to insert new.
BEGIN TRANSACTION
UPDATE t
SET
t.count = t.count + 1
FROM Table1 t
WHERE t.data = #data
IF (##ROWCOUNT = 0)
BEGIN
INSERT INTO Table1
(data, count)
VALUES
(#data, 1)
END
COMMIT TRANSACTION
The explicit transaction is the cost of doing business with a conditional INSERT/UPDATE in order to address concurrency. The example below uses locking hints to a avoid race condition with this code.
BEGIN TRANSACTION;
INSERT INTO Table1
( data
, count
)
SELECT #data
, 1
WHERE NOT EXISTS ( SELECT 1
FROM Table1 WITH ( UPDLOCK, HOLDLOCK )
WHERE data = #data );
IF ##ROWCOUNT = 0
UPDATE Table1
SET count = count + 1
WHERE data = #data;
COMMIT;
If the more common path is the UPDATE, try that first followed by the conditional INSERT.

Raiserror in SQL Server stored procedure

I already created the error in sys.message. The problem is when I add it to my stored procedure, it doesn't pass me the message back. The stored procedure checks to see if an id exists in a certain areacode if the id does not exists the raiserror should be fired.
AS
BEGIN
DECLARE #Result as int
IF EXISTS(SELECT ID, areacode
FROM Table1
WHERE ID = #ID
AND areacode = #areacode)
RAISERROR (50030, 1, 1)
BEGIN
INSERT INTO Table2 ( //columns go here )
VALUES ( //values for columns )
END
DECLARE #Result as int
IF EXISTS(SELECT 1
FROM Table1
WHERE ID=#ID
AND areacode=#areacode
)
BEGIN
INSERT INTO Table2 (/* columns go here */)
VALUES (/* values for columns */)
END ELSE BEGIN
RAISERROR (50030,1,1)
END
Your severity is to low, try to set it a bit higher:
raiserror (50030,16,1)
Btw. why do you try to raise the error right before your insert?
https://msdn.microsoft.com/en-us/library/ms178592.aspx

Update Trigger, set value for the inserted data SQL Server

I need to create a trigger, if update is assigning a user. And we have not set the value before.
I have this but server doesn't like it.... I get an error
Incorrect syntax near '.'
on the 'set' line.
CREATE TRIGGER dbo.trgIssueAcknowledged
ON dbo.hdIssues
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
If (select inserted.AssignedToUserID from inserted) IS NULL
begin
return
end
If (select inserted.AssignedToUserID from inserted) = 0
begin
return
end
If (select inserted.AcknowledgeDate from inserted) IS NULL
begin
set inserted.AcknowledgeDate = GETDATE ( );
end
END
GO
Your trigger has many issues, it doesn't use the real table you want to update, it assumes only one row is modified every time, and the logic with the RETURN is too weird.
CREATE TRIGGER dbo.trgIssueAcknowledged
ON dbo.hdIssues
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
UPDATE A
SET A.AcknowledgeDate = GETDATE()
FROM dbo.hdIssues A
INNER JOIN INSERTED I
ON A.TheKeyOfTheTable = I.TheKeyOfTheTable
WHERE A.AcknowledgeDate IS NULL
AND ( A.AssignedToUserID <> 0 AND A.AssignedToUserID IS NOT NULL)
END
Could it perhaps be the spelling mistake?
inserted.AcknoledgeDate
looks like it is missing "w"
should be
inserted.AcknowledgeDate
Try This
CREATE TRIGGER dbo.trgIssueAcknowledged ON dbo.hdIssues
AFTER INSERT, DELETE, UPDATE
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
DECLARE #INSERTCOUNT INT
DECLARE #DELETECOUNT INT
SET #INSERTCOUNT = ( SELECT COUNT(*)
FROM INSERTED
)
SET #DELETECOUNT = ( SELECT COUNT(*)
FROM DELETED
)
IF #INSERTCOUNT > 0
AND #DELETECOUNT = 0
BEGIN
-- Insert statements for trigger here
INSERT INTO dbo.hdIssues
( col1, col2)
SELECT I.col1,
I.col2
FROM INSERTED I
END
ELSE
IF #INSERTCOUNT > 0
AND #DELETECOUNT > 0
BEGIN
INSERT INTO dbo.hdIssues
( col1, col2)
SELECT I.col1,
I.col2
FROM INSERTED I
INNER JOIN DELETED D ON I.col1 = D.col1
WHERE I.col1 <> D.col1
OR
I.col2 <> D.col2
END
END TRY
BEGIN CATCH
END CATCH
END
GO
Well first of all inserted is a virtual table, so if you want to update one of the columns, you need to do an UPDATE.
UPDATE inserted
set AcknowledgeDate = GETDATE ( )
WHERE AcknowledgeDate IS NULL;
But second of all, why do you want to update the value in the inserted table in an AFTER UPDATE trigger? What are you really trying to do?

Resources