all id columns has auto_increment
In my trigger:
ALTER trigger [dbo].[mytrig]
on [dbo].[requests]
after INSERT, UPDATE
as
begin
declare #MyId1 int
set #MyId1 = (select Id from inserted)
declare #MyId2 int
declare #MyId3 int
if (select column1 from inserted) = 1
begin
insert into [dbo].[contracts] select column1,column2,column3 .... from inserted
set #MyId2 = SCOPE_IDENTITY()
insert into [dbo].[History] select column1,column2,column3 .... from inserted
set #MyId3 = SCOPE_IDENTITY()
insert into [dbo].[contracts_depts](Id_Contract ,column5) select #MyId2,column6 from request_depts where Id_request=#MyId1
insert into [dbo].[History_depts] (Id_InHistory,column5) select #MyId3,column6 from request_depts where Id_request=#MyId1
end
end
#MyId1 returns value only after update but not after insert. Do I have to use scope_identity() or something ?
Your main issue is: you're assuming the triggers is called once per row - that is NOT the case!
The trigger is called once per statement, and if your statement affects multiple rows, the Inserted pseudo table will contain multiple rows - so your statement here
set #MyId1 = (select Id from inserted)
really isn't going to work - it will select one arbitrary row (out of however many there are).
You'll need to rewrite your trigger to take this fact into account! Assume that Inserted contains 100 rows - how do you want to deal with that? What are you trying to achieve? Triggers don't return values - they will record into an audit table, or update other rows, or something like that ....
Related
I have something like the table below:
CREATE TABLE updates (
id INT PRIMARY KEY IDENTITY (1, 1),
name VARCHAR (50) NOT NULL,
updated DATETIME
);
And I'm updating it like so:
INSERT INTO updates (name, updated)
VALUES
('fred', '2020-11-11),
('fred', '2020-11-11'),
...
('bert', '2020-11-11');
I need to write an after update Trigger and enumerate all the name(s) that were added and add each one to another table but can't work out how enumerate each one.
EDIT: - thanks to those who pointed me in the right direction, I know very little SQL.
What I need to do is something like this
foreach name in inserted
look it up in another table and
retrieve a count of the updates a 'name' has done
add 1 to the count
and update it back into the other table
I can't get to my laptop at the moment, but presumably I can do something like:
BEGIN
SET #count = (SELECT UCount from OTHERTAB WHERE name = ins.name)
SET #count = #count + 1
UPDATE OTHERTAB SET UCount = #count WHERE name = ins.name
SELECT ins.name
FROM inserted ins;
END
and that would work for each name in the update?
Obviously I'll have to read up on set based SQL processing.
Thanks all for the help and pointers.
Based on your edits you would do something like the following... set based is a mindset, so you don't need to compute the count in advance (in fact you can't). It's not clear whether you are counting in the same table or another table - but I'm sure you can work it out.
Points:
Use the Inserted table to determine what rows to update
Use a sub-query to calculate the new value if its a second table, taking into account the possibility of null
If you are really using the same table, then this should work
BEGIN
UPDATE OTHERTAB SET
UCount = COALESCE(UCount,0) + 1
WHERE [name] in (
SELECT I.[name]
FROM Inserted I
);
END;
If however you are using a second table then this should work:
BEGIN
UPDATE OTHERTAB SET
UCount = COALESCE((SELECT UCount+1 from OTHERTAB T2 WHERE T2.[name] = OTHERTAB.[name]),0)
WHERE [name] in (
SELECT I.[name]
FROM Inserted I
);
END;
Using inserted and set-based approach(no need for loop):
CREATE TRIGGER trg
ON updates
AFTER INSERT
AS
BEGIN
INSERT INTO tab2(name)
SELECT name
FROM inserted;
END
I have 3 tables tbl_Users, tbl_Protocol and tbl_ProtocolDetails and inside of my trigger on Users, I have to inserted into Protocol and then insert into ProtocolDetails, but I don't know how work the inserted scope.
Something like that:
CREATE TRIGGER tg_Users ON tbl_Users
AFTER INSERT, UPDATE AS
BEGIN
DECLARE #UserId = Int
DECLARE #ProtocolId = Int
DECLARE #UserDetail = NVARCHAR(255)
SELECT
#UserId = user_id,
#UserDetail = user_detail + '#' + user_explanation
FROM INSERTED
INSERT INTO tbl_Protocol (user_id, inserted_date)
VALUES (#UserId, GetDate())
-- Return Inserted Id from tbl_Protocol into #ProtocolDetail then
INSERT INTO tbl_ProtocolDetails (protocol_id, protocol_details)
VALUES (#ProtocolId, #UserDetail)
END
Your trigger has a MAJOR flaw in that you seems to expect to always have just a single row in the Inserted table - that is not the case, since the trigger will be called once per statement (not once for each row), so if you insert 20 rows at once, the trigger is called only once, and the Inserted pseudo table contains 20 rows.
Therefore, code like this:
Select #UserId = user_id,
#UserDetail = user_detail + '#' + user_explanation
From INSERTED;
will fail, since you'll retrieve only one (arbitrary) row from the Inserted table, and you'll ignore all other rows that might be in Inserted.
You need to take that into account when programming your trigger! You have to do this in a proper, set-based fashion - not row-by-agonizing-row stlye!
Try this code:
CREATE TRIGGER tg_Users ON tbl_Users
AFTER INSERT, UPDATE AS
BEGIN
-- declare an internal table variable to hold the inserted "ProtocolId" values
DECLARE #IdTable TABLE (UserId INT, ProtocolId INT);
-- insert into the "tbl_Protocol" table from the "Inserted" pseudo table
-- keep track of the inserted new ID values in the #IdTable
INSERT INTO tbl_Protocol (user_id, inserted_date)
OUTPUT Inserted.user_id, Inserted.ProtocolId INTO #IdTable(UserId, ProtocolId)
SELECT user_id, SYSDATETIME()
FROM Inserted;
-- insert into the "tbl_ProtocolDetails" table from both the #IdTable,
-- as well as the "Inserted" pseudo table, to get all the necessary values
INSERT INTO tbl_ProtocolDetails (protocol_id, protocol_details)
SELECT
t.ProtocolId,
i.user_detail + '#' + i.user_explanation
FROM
#IdTable t
INNER JOIN
Inserted i ON i.user_id = t.UserId
END
There is nothing in this trigger that would handle a multiple insert/update statement. You will need to either use one scenario that will handle multiple records or check how many records were effected with a IF ##ROWCOUNT = 1 else statement. In your example, I would just use something like
insert into tbl_Protocol(user_id, inserted_date)
select user_id, user_detail + '#' + user_explanation
From INSERTED;
As for your detail table, I see Marc corrected his answer to include the multiple lines and has a simple solution or you can create a second trigger on the tbl_Protocol. Another solution I have used in the past is a temp table for processing when I have very complicated triggers.
I was saving a set of values (1 record) to a table in this way:
UPDATE Table SET ...
IF ##ROWCOUNT = 0 INSERT INTO Table ...
This worked fine, but now I need to add more independent records at the same time, so I tried something like:
UPDATE Table SET ...
IF ##ROWCOUNT = 0 INSERT INTO Table ...
UPDATE Table SET ...
IF ##ROWCOUNT < 2 INSERT INTO Table ...
UPDATE Table SET ...
IF ##ROWCOUNT < 3 INSERT INTO Table ...
UPDATE Table SET ...
IF ##ROWCOUNT < 4 INSERT INTO Table ...
...in order to keep it in 1 transaction. However, starting from the 2nd declaration it adds rows even if the particular row is already existing - apparently ##ROWCOUNT does not count INSERTed rows, or I misunderstood its concept another way...
I wonder if there is some feasible way to resolve this within SQL query. In this particular case, it should be OK to assume that all records are dependent on existence of the first, but it is not 100% reliable and I would like to know a better solution, if exists.
EDIT - solution:
This may not be possible in all applications (be aware, that particular updates are not checked separately), but in my case it apears to work well and should be also consistent with the data, because the set of updated/inserted data is integral.
UPDATE Table SET ...
UPDATE Table SET ...
UPDATE Table SET ...
UPDATE Table SET ...
IF ##ROWCOUNT = 0
BEGIN
INSERT INTO Table ...
INSERT INTO Table ...
INSERT INTO Table ...
INSERT INTO Table ...
END
A slightly cleaner solution to you problem may be to use:
DECLARE #Reasons TABLE ( Name NVARCHAR(50) PRIMARY KEY, ReasonType NVARCHAR(50))
INSERT INTO #Reasons ( Name, ReasonType ) VALUES ( N'Review', N'Old Reason' )
SELECT * FROM #Reasons
MERGE INTO #Reasons AS Target
USING (VALUES
('Recommendation','Other'),
('Review', 'Marketing'),
('Internet', 'Promotion')
)
AS Source (NewName, NewReasonType)
ON Target.Name = Source.NewName
WHEN MATCHED THEN
UPDATE SET ReasonType = Source.NewReasonType
WHEN NOT MATCHED BY TARGET THEN
INSERT (Name, ReasonType) VALUES (NewName, NewReasonType) ;
SELECT * FROM #Reasons
Then you'll get all or nothing, without all of the conditions. See https://msdn.microsoft.com/en-us/library/bb510625.aspx details on the merge statement.
UPDATE TABLE .....
IF(condition)
BEGIN
/*
do insert here
*/
END
IF(condition)
BEGIN
/*
do insert here
*/
END
Source: SQL Tutorials
I need to write a trigger based on the following condition
Before inserting a record in the table, I need to compare the value of one column to the existing records, and if records found then I need to delete those records having same column value in the already existing records, and then need to insert that new record.
Please let me know how to achieve this.
Thanks
CREATE TRIGGER [dbo].[CustomInsert_Trigger] ON [dbo].[Realtimebookingcount]
INSTEAD OF INSERT AS
BEGIN
DECLARE #Flag INT
SELECT #Flag = Booking_NUM FROM inserted
IF (SELECT COUNT(1) FROM Realtimebookingcount
WHERE Booking_NUM = #Flag) > 0
BEGIN
DELETE FROM Realtimebookingcount
WHERE Realtimebookingcount.Booking_NUM = #Flag
END
INSERT INTO Realtimebookingcount
SELECT * FROM inserted
END
I use this code to check if an element of the new entry is equal to an element of previously inserted data.
CREATE TRIGGER trig1 ON Table1
AFTER INSERT
AS
DECLARE trigcursor CURSOR FOR
SELECT Name FROM INSERTED
DECLARE #Name1 varchar(80)
OPEN trigcursor;
FETCH NEXT FROM trigcursor INTO #Name1
WHILE ##FETCH_STATUS = 0
BEGIN
IF EXISTS(SELECT * FROM Table1 WHERE Name= #Name1)
BEGIN
...
END
FETCH NEXT FROM trigcursor INTO #Name1
END
The problem is that for some reason the new entry exists also in the table Table1, not only in INSERTED. So the condition is always true. Can you help me why this happens? Is there a way to retrieve only the initial table without the new entry in it? Thanks!
Your trigger is AFTER INSERT on table Table1. It should be BEFORE INSERT if you expect not to find the record in the table.
Alternative: use INSTEAD OF INSERT trigger.
OR
Add another column that accepts null. Make it a number column so that it will be fast. Do not insert any value in it on the insert. Then, in the AFTER INSERT TRIGGER, the rows that have that column empty are the new ones. The ones that have the column filled with something are the old ones.
Then update empty columns with value.
eg: add column mark
After insert, look for the name:
SELECT * FROM Table1 WHERE Name= #Name1 and mark is not null
Once you found out whether or not it existed before, update everything with something:
update table1 set mark = 1 where mark is null