My Trigger not work - sql

I have trigger after insert in Table1 will insert in Table2 , after update columnA in Table1 will update in Table2 Status_Column where Table1.id=table2.id, after delete row in table1 will delete row in table2 where table1.id=table2.id.
create TRIGGER mytrigger
ON Table1
after UPDATE,INSERT,DELETE
AS
Begin
if update (columnA)
BEGIN
SET NOCOUNT ON;
declare #ID int
select #ID = ID from DELETED
UPDATE Table2
SET T2.Status_Column ='Updated'
From Table2 T2
inner join Table1 T1 on T1.ID = T2.ID
where T1.ID = #ID
End
else if exists (select * from inserted)
Begin
declare #table2_ID int
select #table2_ID = ID from inserted
insert into table2 (ID,Status_Column) values (#table2_ID,'New')
End
else if exists (select ID from deleted)
Begin
delete from table2
from table2 T2 , Deleted d
where t2.id= d.id
End
End
Only the delete statement working :(

Your fundamental flaw is that you seem to expect the trigger to be fired once per row - this is NOT the case in SQL Server. Instead, the trigger fires once per statement, and the pseudo table Deleted might contain multiple rows.
Given that that table might contain multiple rows - which one do you expect will be selected here??
select #ID = ID from DELETED
It's undefined - you might get the values from arbitrary rows in Deleted.
You need to rewrite your entire trigger with the knowledge the Deleted WILL contain multiple rows! You need to work with set-based operations - don't expect just a single row in Deleted !

Related

Trigger to update foreign key field after insert on same table

I have two tables:
Table1 (surveyid [PKID], surveyname)
Table2 (visitid [PKID], surveyname, surveyid [FKID - refers to Table1]).
After inserting a new row into Table2, I would like to update Table2.surveyid with the surveyid from Table1, based on matching surveyname.
I thought it maybe wasn't possible (or good practice?) to create a trigger to update the same table. But I seem to have created a trigger that will do this. The problem is that after insert, the trigger updates the surveyid for every row, instead of just the newly inserted rows.
This trigger code works, but how do I ensure the trigger only updates the surveyid for newly inserted rows, and not all rows?
CREATE TRIGGER tr_update_table2_fk
ON Table2
AFTER INSERT
AS
BEGIN
UPDATE Table2
SET surveyid = (SELECT t1.surveyid
FROM Table1 t1
WHERE t1.surveyname = Table2.surveyname)
END;
Thank you MattM and DaleK, you've helped me figure out the answer. I was adding the inserted table into the subquery where clause before, instead of the query where clause. This does the trick:
CREATE TRIGGER tr_update_table2_fk
on Table2
AFTER INSERT
AS
BEGIN
UPDATE Table2 SET
surveyid = (
SELECT t1.surveyid
FROM Table1 t1
WHERE t1.surveyname = Table2.surveyname
)
WHERE Table2.visitid IN (SELECT visitid FROM inserted)
END;
Yes, the inserted table is the answer.
I'd use it to capture the visitids of the inserted rows, then filter by them in a WHERE clause on the end of your UPDATE statement in your trigger.
E.g.
CREATE OR ALTER TRIGGER tr_update_table2_fk
ON Table2
AFTER INSERT
AS
BEGIN
DROP TABLE IF EXISTS #VisitIds ;
CREATE TABLE #VisitIds ( [id] INT ) ;
INSERT INTO #VisitIds ( [id] )
SELECT [visitid]
FROM inserted ;
UPDATE Table2
SET [surveyid] =
(
SELECT t1.[surveyid]
FROM Table1 AS t1
WHERE t1.[surveyname] = Table2.[surveyname]
)
WHERE [visitid] IN ( SELECT [id] FROM #VisitIds ) ;
END
GO

How to optimize a trigger?

CREATE TRIGGER T
ON TABLE_2
AFTER INSERT
AS
DECLARE #bought_t int,
#name_t varchar(20)
SELECT #name_t = name_t
FROM inserted
SELECT #bought_t = bought_t
FROM TABLE_1
WHERE name_t = #name_t
IF #bought_t < 100
BEGIN
UPDATE TABLE_1
SET bought_t = #bought_t + 1
WHERE TABLE_1.name_t = #name_t
END
ELSE
ROLLBACK TRANSACTION
The column (TABLE_1) I'm making the update to after the insert happens in the 'TABLE_2' is supposed to hold values between 50 and 100. So I'm asking If this trigger is as professional and optimized as It could be? or I have some flaws that could lead to bugs/security issues.
Basically, you need to completely rewrite your trigger to be set-based and to be able to work with multiple rows in the Inserted pseudo table.
Fortunately, that also makes it easier - in my opinion - try something like this:
CREATE TRIGGER T
ON TABLE_2
AFTER INSERT
AS
UPDATE T1
SET bought_t = bought_t + 1
FROM TABLE_1 T1
INNER JOIN Inserted i ON i.name_t = T1.name_t
WHERE T1.bought_t < 100
UPDATE: demo to prove this works:
-- create the two tables
CREATE TABLE TABLE_2 (ID INT NOT NULL IDENTITY(1,1), ProdName VARCHAR(50))
CREATE TABLE TABLE_1 (ProdName VARCHAR(50), Bought INT)
GO
-- create trigger on "TABLE_2" to update "TABLE_1"
CREATE TRIGGER T2Insert
ON TABLE_2
AFTER INSERT
AS
UPDATE T1
SET Bought = Bought + 1
FROM TABLE_1 T1
INNER JOIN Inserted i ON T1.ProdName = i.ProdName
WHERE T1.Bought < 100
GO
-- initialize TABLE_1 with some seed data
INSERT INTO dbo.TABLE_1 (ProdName, Bought)
VALUES ( 'Prod1', 0), ('Prod2', 20), ('Prod3', 40), ('Prod4', 40), ('Prod100', 100)
-- insert new values into TABLE_2
INSERT INTO dbo.TABLE_2 (ProdName)
VALUES ('Prod1'), ('Prod100'), ('Prod2'), ('Prod4')
-- get data to check
SELECT * FROM dbo.TABLE_1
This renders output:
As you can easily see:
Prod1, Prod2, Prod4 that were inserted caused an update of the value Bought
Prod100 which was also inserted did not cause an update of Bought
UPDATE #2: if you need to be able to insert multiple identical values at once, you need to slightly enhance your trigger like this:
CREATE TRIGGER T2Insert
ON TABLE_2
AFTER INSERT
AS
-- declare table variable to hold names and update counts
DECLARE #UpdateCount TABLE (Name VARCHAR(50), UpdCount INT)
-- from the "Inserted" table, determine which names are being
-- inserted how many times using GROUP BY
INSERT INTO #UpdateCount (Name, UpdCount)
SELECT ProdName, COUNT(*)
FROM Inserted
GROUP BY ProdName
-- now join to this temporary table, and update as many times
-- as needed (instead of +1 for all cases)
UPDATE T1
SET Bought = Bought + uc.UpdCount
FROM TABLE_1 T1
INNER JOIN #UpdateCount uc ON uc.Name = T1.ProdName
WHERE T1.Bought < 100
GO

Triggers execution after group update with "in (...)" statement in query

I have AFTER UPDATE trigger for in table:
ALTER TRIGGER [dbo].[table1]
ON [dbo].[table]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #primaryKey bigint
SELECT #PrimaryKey = PK FROM Inserted
if EXISTS(select * from [dbo].[table1] where PK=#PrimaryKey)
begin
update [dbo].[table1] set [Action] = 'U' where PK=#PrimaryKey
end
else
begin
insert into [dbo].[table1] ([PK], [Action], StampIn)
values (#PrimaryKey, 'U', GETDATE())
end
END
When I do "update SOME_DB.dbo.TABLE set FIELD='NEW VALUE' where PK in (3,4,5)", I found that only one row was added to the table1 with PK "3". Which means that trigger was executed only once in table.
But I need to have all the rows in table1 with PKs that were updated.
Could you please help me to solve my problem?
Thank you.
SQL triggers use the inserted view to identify all the rows being inserted. Your logic is only looking at one of the rows; hence it does not do what you expect. So:
BEGIN
SET NOCOUNT ON;
update t1
set [Action] = 'U'
from table1 t1 join
inserted i
on i.primarykey = t1.pk ;
insert into [dbo].[table1] ([PK], [Action], StampIn)
select i.primarykey, 'U', getdate()
from inserted i
where not exists (select 1 from dbo.table1 t1 where t1.pk = i.primarykey);
END;
You don't actually need the conditional logic, because the join and where clauses take care of that.

please help me to create multi insert query

I have got two table
create table t1(cid int, isnews int)
create table t2(nid int,cid int, isnews int)
situations is like this:
if t2 contain t2.cid = t1.cid then the t2.isnews = t1.news and
if t2 not contain cid of t1 then new record should be inserted in t2 and that t1.cid, t1.isnews should be inserted in t2..
and complete table should be done in single query... i have done the updation part but not able to do insertion part..
update query:
UPDATE t22
SET t22.isnews = t11.isnews
FROM t2 AS t22
JOIN t1 AS t11
ON t11.cid= t22.cid
i have prepared below cursor for insert... is it good? :
DECLARE #clntid INT
DECLARE #clntnewsltr INT
DECLARE clientnews CURSOR FOR
SELECT clientid,newsLetter
FROM clients
WHERE clientid NOT IN (SELECT clientid FROM clientprivacy)
OPEN clientnews
FETCH NEXT FROM clientnews INTO #clntid,#clntnewsltr
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO clientprivacy (clientId,tdNewsLetters) VALUES(#clntid, #clntnewsltr)
FETCH NEXT FROM clientnews INTO #clntid,#clntnewsltr
END
CLOSE clientnews
DEALLOCATE clientnews
I think this is the kind of thing you're after:
--INSERT t2 (cid, isnews)
SELECT t1.cid, t1.isnews
FROM t1
LEFT JOIN t2 ON t1.cid = t2.cid
WHERE t2.cid IS NULL
I've commented out the INSERT line - I recommend you run the SELECT on it's own first to check it does give you the correct result (all records from t1 that don't have a matching cid in t2).
I've assumed t2.nid is an IDENTITY column.
You will be so much better off without cursors :) Cursors take MUCH longer to run in large data sets.
It is true you can use a LEFT JOIN, but you can also use a SELECT in your WHERE clause. Most of the time it's a style choice.
CREATE TABLE table1(col_1 int, col_2 int)
CREATE TABLE table2(nid int, col_1 int, col_2 int)
INSERT INTO table2 (col_1,col_2)
SELECT col_1,col_2
FROM table1
WHERE col_1 NOT IN (SELECT col_1 FROM table2)

Use APPLY with an UPDATE statement as the row source

I tried to perform the following in order to update a psuedo-identity value at the same time as using the value to create new rows, but APPLY does not like UPDATE statements as the right table source. What's the most elegant alternative outside of simply using an identity column?
create table Temp1(
id int not null identity(1,1) primary key
,data nvarchar(max) null)
create table Temp2(
id int not null primary key
,fkTemp1 int not null references Temp1(id)
,data nvarchar(max) null)
create table Numbering(
ObjectCode int not null primary key
,AutoKey int)
insert into Temp1(data) values('test string')
insert into Temp1(data) values('another test string')
insert into Numbering(ObjectCode, AutoKey) values(4, 1)
insert into Temp2(id, fkTemp1, data)
select n.AutoKey, t1.id, t1.data
from Temp1 t1
left join Temp2 t2 on t2.fkTemp1 = t1.id
cross apply (update Numbering set AutoKey = AutoKey + 1 output inserted.AutoKey where ObjectCode = 4) n
where t2.id is null -- only insert where a target row does not already exist
You cannot do an INSERT and UPDATE on two different tables in one statement in SQL Server 2005.
In SQL Server 2008 there is MERGE construct, however, it works only on single table.
Just run two statements in a transaction:
BEGIN TRANSACTION
DECLARE #AutoKey INT
SELECT #AutoKey = AutoKey
FROM Numbering WITH (UPDLOCK)
WHERE ObjectCode = 4
INSERT
INTO temp2
SELECT #AutoKey + ROW_NUMBER() OVER (ORDER BY id), id, data
FROM temp1
WHERE id NOT IN
(
SELECT fkTemp1
FROM temp2
)
UPDATE Numbering
SET AutoKey = AutoKey + ##ROWCOUNT
WHERE ObjectCode = 4
COMMIT
Update:
As #Remus Rusanu pointed out, you actually can pipeline UPDATE output clause into a table in SQL Server 2005.
However, it seems you can neither JOIN nor CROSS APPLY the OUTPUT resultset to the result of other queries.
This will do it, but you'll have to fix the "T2.ID IS NULL" problem...
Declare #Key as int
Declare #cnt as int
Begin Transaction
Set #cnt = (Select count(*)
from Temp1 t1 left join Temp2 t2 on t2.fkTemp1 = t1.id
--where t2.id is null -- note: does not work, not sure what is intended
)
update Numbering set #Key = AutoKey = AutoKey + #cnt where ObjectCode = 4
insert into Temp2(id, fkTemp1, data)
select #Key+ROW_NUMBER() over (Order By t1.id)
, t1.id, t1.data
from Temp1 t1
left join Temp2 t2 on t2.fkTemp1 = t1.id
--where t2.id is null -- note: does not work,
Commit Transaction