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

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

Related

Multiple select queries execution one after other

I am having six select queries with different where conditions if first select query returns null it should check the next select query and follows. what is the best approach to follow for writing it as stored procedure in SQL server.
You can use ##rowcount
DECLARE #OperatorID INT = 4, #CurrentCalendarView VARCHAR(50) = 'month';
declare #t table (operatorID int, CurrentCalendarView varchar(50));
insert into #t values (2, 'year');
select operatorID - 1, CurrentCalendarView from #t where 1 = 2
if (##ROWCOUNT = 0)
begin
select operatorID + 1, CurrentCalendarView from #t where 1 = 1
end
If I understand your question correctly then you can achieve this like below sample. You can go in this way.
if NOT EXISTS (SELECT TOP(1) 'x' FROM table WHERE id =#myId)
BEGIN
IF NOT EXISTS (SELECT TOP(1) 'x' FROM table2 WHERE id = #myId2)
BEGIN
IF NOT EXISTS (SELECT TOP(1) 'x' FROM table 3 WHERE id = #myID3)
BEGIN
END
END
END

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.

Safest way to get the last inserted ID to be unique - SQL

I know that the SCOPE_IDENTITY() will get the last inserted row from insert statement. However, for the following case, I am not too sure is SCOPE_IDENTITY() is safe. As SELECT MAX(ID) FROM TableA will have go through scan the table to get the max id and it will have performance issue, even slightly, I believe.
Here is the case:
DECLARE #DaysInMonth INT
DECLARE #FirstID INT
DECLARE #SecondID INT
DECLARE #ThirdID INT
DECLARE #FourthID INT
SET #DaysInMonth = DAY(EOMONTH('2016-09-01'))
BEGIN TRY
BEGIN TRANSACTION
WHILE #DaysInMonth > 0
BEGIN
-- First Insert -- Begin
INSERT INTO tableA ('first insert - ' + #DaysInMonth)
-- First Insert -- End
SET #FirstID = SCOPE_IDENTITY()
-- Second Insert -- Begin
INSERT INTO tableB ('second insert - ' + #DaysInMonth)
-- Second Insert -- End
SET #SecondID = SCOPE_IDENTITY()
-- Third Insert -- Begin
INSERT INTO tableC ('third insert - ' + #DaysInMonth)
-- Third Insert -- End
SET #ThirdID = SCOPE_IDENTITY()
-- Fourth Insert -- Begin
INSERT INTO tableD ('fourth insert - ' + #DaysInMonth)
-- Fourth Insert -- End
SET #FourthID = SCOPE_IDENTITY()
SET #DaysInMonth = #DaysInMonth - 1
END
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION
THROW
END CATCH
As from the case above, I have to insert the records every loop for fourth times for how many days in the month that I have declared.
From what I know, there are 4 to get the last inserted ID:
SCOPE_IDENTITY
##IDENTITY
SELECT MAX(ID) FROM tableA
IDENT_CURRENT
From the following post:
Post
Is mentioned that SCOPE_IDENTITY() is what generally that you want to use.
What I mean with 'Safe' is, do the ID will be unique during the loop?
Thank you.
You can use OUTPUT column in the last insert statement, Ofcourse this is another option where you will get what exactly input statement executed.. Below is just an example
CREATE TABLE #tablea (
id int IDENTITY (1, 1),
val char(10)
)
DECLARE #outputtbl TABLE (
id int,
val char(10)
)
INSERT INTO #tablea (val)
OUTPUT INSERTED.* INTO #outputtbl
VALUES ('test')
SELECT id
FROM #outputtbl

MERGE in SQL Server

I am trying to perform the following using a MERGE statement
I have a table that has two columns (TagId is an Identity (PK) and Name as a VARCHAR). I would like to check first if Name exists before I insert it. If it does exist, I would like to get the Identity value. Otherwise, I would insert it and pick up the inserted value.
The beauty of MERGE is it is transactional in nature. so, I won't have to worry about getting an UNIQUE index violation due to timing. Any suggestions? I prefer not to use transactions.
DECLARE
#TagId INT,
#Name VARCHAR(100) = 'TagName'
SELECT TOP(1)
#TagId = T.TagId
FROM dbo.Tag AS T
WHERE T.Name = #Name
IF #TagId IS NULL
BEGIN
INSERT dbo.Tag (Name) VALUES (#Name)
SELECT #TagId = SCOPE_IDENTITY()
END
After trying, this seems to work: it doesn't seem right. The MATCHED clause is required. Otherwise, #Table won't have value.
DECLARE #Table TABLE
(
TagId INT,
Name VARCHAR(100)
)
DECLARE
#TagId INT,
#Name VARCHAR(100) = 'TdagNamed122'
MERGE dbo.Tag AS Target
USING(SELECT #Name) AS Source (Name)
ON Source.Name = Target.Name
WHEN MATCHED THEN
UPDATE SET #TagId = Target.TagId
WHEN NOT MATCHED THEN
INSERT (Name) VALUES (Source.Name)
OUTPUT INSERTED.* INTO #Table
;
SELECT * FROM #Table
Yes as per the documentation , it should execute all statements in atomic fashion
I didn't face any concurrency issues
But There are some concerns as per the link
http://www.mssqltips.com/sqlservertip/3074/use-cauti

T-SQL Insert into temp table from another temp table

I am trying to acheive like this at the end of the procedure i need all the rows in
one temp table
How can I accomplish this
if #i > 1
begin
select * from into #tempTbl1 from payments
where method = 'test1'
end
else
begin
select * from into #tempTbl2 from payments
where method = 'test1'
end
insert into #tempTbl1 select * from #tempTbl2
select * from #tempTbl1
despite the previous logic issue, to simplely get all rows from both temp tables , use UNION:
select * from #tempTbl1
UNION ALL
SELECT * from #tempTbl2
The problem you have here is that based on your IF/ELSE you will never had both tables. Your final INSERT INTO requires that both tables exist. You may need to create the objects before hand in the stored proc before you try to populate, then insert into the tables.
This also begs the question, if you're going to later insert everything in #tempTbl1 anyways, which is created in the SELECT INTO statement, why have the #tempTbl2 in the first place?
create procedure dbo.testing
(#i int)
AS
if #i > 1
begin
print 'In condition 1'
select *
into #tempTbl1
from payments
where method = 'test1'
end
else
begin
print 'In condition 2'
select *
into #tempTbl2
from payments
where method = 'test1'
end
print 'Made it out of the if else'
insert into #tempTbl1
select *
from #tempTbl2
-- Never gets this far...
print 'In the final select'
select *
from #tempTbl1
If you're committed to this method, then you may need to check to see if the table exists:
IF EXISTS (SELECT * FROM tempdb.sys.objects WHERE object_id = OBJECT_ID(N'tempdb.dbo.#tempTbl1') AND type in (N'U'))
print 'Table is there'
Update based on comments
Based on your comments, this will work. The SELECT...INTO statement you originally posted lets you create a table based on the data types of the columns your selecting from, but the destination table can't already exist. If you define the structure you're going to insert into beforehand, you can have the two conditions evaluate and end up with a single table as the result.
(Note - my "payments" table only had two columns, "method" and "col2". You would want to specify the columns you need in the CREATE TABLE and the SELECT)
create procedure dbo.testing
(#i int)
AS
create table #tempTbl1
(method varchar(10)
, col2 int)
if #i > 1
begin
insert into dbo.#tempTbl1
select method, col2
from payments
where method = 'test1'
end
else
begin
insert into dbo.#tempTbl1
select method, col2
from payments
where method = 'test1'
end
select *
from #tempTbl1

Resources