I have to create query that after delete will insert data in other table. But I've deleted 3 rows in my table, but table with insert has only one row. And it's the first row that was deleted.
This is my trigger:
CREATE trigger trigger1
ON Sales.SalesPerson
FOR DELETE
AS
BEGIN
DECLARE #ID int
SELECT #ID = BusinessEntityID FROM deleted
INSERT INTO dbo.Deleted(ID)
VALUES (#ID)
PRINT 'TRIGGER'
END
What did I do wrong?
In SQL Server, triggers fire per operation, not per row.
If you delete three rows, your SELECT assignment is going to (at least logically) assign each of those three values to the variable one at a time, and so the value that ultimately ends up in your logging table is the arbitrary value that happened to be assigned last.
You can simulate this as follows:
DECLARE #id int;
SELECT #id = database_id FROM sys.databases;
PRINT #id;
There are multiple rows in sys.databases, why did only one value get printed?
Instead of using a scalar variable and expecting it to somehow hold multiple values (or for the insert to happen multiple times), you need to insert as a set in a single operation:
INSERT dbo.Deleted(ID)
SELECT BusinessEntityID from deleted;
Further reading.
Related
My database contains three tables called Object_Table, Data_Table and Link_Table. The link table just contains two columns, the identity of an object record and an identity of a data record.
I want to copy the data from DATA_TABLE where it is linked to one given object identity and insert corresponding records into Data_Table and Link_Table for a different given object identity.
I can do this by selecting into a table variable and the looping through doing two inserts for each iteration.
Is this the best way to do it?
Edit : I want to avoid a loop for two reason, the first is that I'm lazy and a loop/temp table requires more code, more code means more places to make a mistake and the second reason is a concern about performance.
I can copy all the data in one insert but how do get the link table to link to the new data records where each record has a new id?
In one statement: No.
In one transaction: Yes
BEGIN TRANSACTION
DECLARE #DataID int;
INSERT INTO DataTable (Column1 ...) VALUES (....);
SELECT #DataID = scope_identity();
INSERT INTO LinkTable VALUES (#ObjectID, #DataID);
COMMIT
The good news is that the above code is also guaranteed to be atomic, and can be sent to the server from a client application with one sql string in a single function call as if it were one statement. You could also apply a trigger to one table to get the effect of a single insert. However, it's ultimately still two statements and you probably don't want to run the trigger for every insert.
You still need two INSERT statements, but it sounds like you want to get the IDENTITY from the first insert and use it in the second, in which case, you might want to look into OUTPUT or OUTPUT INTO: http://msdn.microsoft.com/en-us/library/ms177564.aspx
The following sets up the situation I had, using table variables.
DECLARE #Object_Table TABLE
(
Id INT NOT NULL PRIMARY KEY
)
DECLARE #Link_Table TABLE
(
ObjectId INT NOT NULL,
DataId INT NOT NULL
)
DECLARE #Data_Table TABLE
(
Id INT NOT NULL Identity(1,1),
Data VARCHAR(50) NOT NULL
)
-- create two objects '1' and '2'
INSERT INTO #Object_Table (Id) VALUES (1)
INSERT INTO #Object_Table (Id) VALUES (2)
-- create some data
INSERT INTO #Data_Table (Data) VALUES ('Data One')
INSERT INTO #Data_Table (Data) VALUES ('Data Two')
-- link all data to first object
INSERT INTO #Link_Table (ObjectId, DataId)
SELECT Objects.Id, Data.Id
FROM #Object_Table AS Objects, #Data_Table AS Data
WHERE Objects.Id = 1
Thanks to another answer that pointed me towards the OUTPUT clause I can demonstrate a solution:
-- now I want to copy the data from from object 1 to object 2 without looping
INSERT INTO #Data_Table (Data)
OUTPUT 2, INSERTED.Id INTO #Link_Table (ObjectId, DataId)
SELECT Data.Data
FROM #Data_Table AS Data INNER JOIN #Link_Table AS Link ON Data.Id = Link.DataId
INNER JOIN #Object_Table AS Objects ON Link.ObjectId = Objects.Id
WHERE Objects.Id = 1
It turns out however that it is not that simple in real life because of the following error
the OUTPUT INTO clause cannot be on
either side of a (primary key, foreign
key) relationship
I can still OUTPUT INTO a temp table and then finish with normal insert. So I can avoid my loop but I cannot avoid the temp table.
I want to stress on using
SET XACT_ABORT ON;
for the MSSQL transaction with multiple sql statements.
See: https://msdn.microsoft.com/en-us/library/ms188792.aspx
They provide a very good example.
So, the final code should look like the following:
SET XACT_ABORT ON;
BEGIN TRANSACTION
DECLARE #DataID int;
INSERT INTO DataTable (Column1 ...) VALUES (....);
SELECT #DataID = scope_identity();
INSERT INTO LinkTable VALUES (#ObjectID, #DataID);
COMMIT
It sounds like the Link table captures the many:many relationship between the Object table and Data table.
My suggestion is to use a stored procedure to manage the transactions. When you want to insert to the Object or Data table perform your inserts, get the new IDs and insert them to the Link table.
This allows all of your logic to remain encapsulated in one easy to call sproc.
If you want the actions to be more or less atomic, I would make sure to wrap them in a transaction. That way you can be sure both happened or both didn't happen as needed.
You might create a View selecting the column names required by your insert statement, add an INSTEAD OF INSERT Trigger, and insert into this view.
Before being able to do a multitable insert in Oracle, you could use a trick involving an insert into a view that had an INSTEAD OF trigger defined on it to perform the inserts. Can this be done in SQL Server?
Insert can only operate on one table at a time. Multiple Inserts have to have multiple statements.
I don't know that you need to do the looping through a table variable - can't you just use a mass insert into one table, then the mass insert into the other?
By the way - I am guessing you mean copy the data from Object_Table; otherwise the question does not make sense.
//if you want to insert the same as first table
$qry = "INSERT INTO table (one, two, three) VALUES('$one','$two','$three')";
$result = #mysql_query($qry);
$qry2 = "INSERT INTO table2 (one,two, three) VVALUES('$one','$two','$three')";
$result = #mysql_query($qry2);
//or if you want to insert certain parts of table one
$qry = "INSERT INTO table (one, two, three) VALUES('$one','$two','$three')";
$result = #mysql_query($qry);
$qry2 = "INSERT INTO table2 (two) VALUES('$two')";
$result = #mysql_query($qry2);
//i know it looks too good to be right, but it works and you can keep adding query's just change the
"$qry"-number and number in #mysql_query($qry"")
I have 17 tables this has worked in.
-- ================================================
-- Template generated from Template Explorer using:
-- Create Procedure (New Menu).SQL
--
-- Use the Specify Values for Template Parameters
-- command (Ctrl-Shift-M) to fill in the parameter
-- values below.
--
-- This block of comments will not be included in
-- the definition of the procedure.
-- ================================================
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE InsetIntoTwoTable
(
#name nvarchar(50),
#Email nvarchar(50)
)
AS
BEGIN
SET NOCOUNT ON;
insert into dbo.info(name) values (#name)
insert into dbo.login(Email) values (#Email)
END
GO
I'm going to create a trigger for update. Purpose of this trigger is that If muadurum column is changed , take the old value of mua_tarih in table fkayitlar and insert to another table mua_tarihleri.
My code block like;
ALTER TRIGGER [dbo].[trgr_fkayit_update]
ON [dbo].[fkayitlar]
AFTER UPDATE
AS
DECLARE #mua_durum_once NVARCHAR(10)
DECLARE #mua_durum_sonra NVARCHAR(10)
DECLARE #mua_tarih_once DATE
DECLARE #mua_yapan_once NVARCHAR(25)
DECLARE #kisi_id INT
Take the old value;
SELECT
#kisi_id=kayitid,
#mua_durum_once=muayenedurum,
#mua_tarih_once=muayenetarih,
#mua_yapan_once=mua_yapan
FROM deleted
Take the new value;
SELECT #mua_durum_sonra=muayenedurum FROM inserted
Check if value is changed ; if changed, Insert #mua_tarih to table mua_tarihleri with #kisi_id and #mua_yapan_once
IF #mua_durum_once='OLDU'
AND #mua_durum_sonra='OLMADI'
AND #mua_tarih_once IS NOT NULL
BEGIN
INSERT INTO mua_tarihleri(kayitid,mua_tarihi,mua_yapan)
VALUES(#kisi_id,#mua_tarih_once,#mua_yapan_once)
END
My problem is When I update more than one row in table fkayitlar,Trigger is working, but I see only one inserted row in table mua_tarihleri (only working once). I need to see more than one.(should be working more than once) Are not Triggers working on more than one process? or How can I solve this my problem?
The trigger only occurs once when the table is updated, no matter how many rows are updated. Therefore, you have to write your trigger body to operate on a set of rows, not a single row as you have done.
Should be something like:
ALTER TRIGGER [dbo].[trgr_fkayit_update]
ON [dbo].[fkayitlar]
AFTER UPDATE
AS
INSERT INTO mua_tarihleri(kayitid,mua_tarihi,mua_yapan)
SELECT deleted.kayitid, deleted.muayenedurum, deleted.muayenetarih, deleted.mua_yapan
FROM deleted
JOIN inserted ON deleted.kayitid = inserted.kayitid
WHERE deleted.muayenedurum='OLDU'
AND inserted.muayenedurum='OLMADI'
AND muayenetarih IS NOT NULL
I have a trigger on a table that is something like this:
ALTER TRIGGER [shoot_sms]
ON [dbo].[MyTable]
AFTER INSERT
AS
begin
declare #number bigint
declare #body varchar(50)
declare #flag int
select #number=number,#body=body,#flag=flag from inserted
if(#flag=0)
begin
insert into temptable (number,body,status)
select #number,#body,'P'
end
end
Now I am making two entries in mytable as below:
insert into mytable(number, body, flag)
values(3018440225, 'This is test', 0)
insert into mytable(number, body, flag)
values(3018440225, 'This is test', 0)
I execute these queries at a time, but for both of the queries the trigger fires only once and performs the task for the first query only.
How can I make it work for both insert statements?
Just an idea but put a GO statement between those two insert statements and that might cause the trigger to fire twice.
You should probably rewrite your trigger to handle multiple row inserts I think.
Here is your query converted. You should get two rows now.
ALTER TRIGGER [shoot_sms]
ON [dbo].[MyTable]
AFTER INSERT
AS
begin
insert into temptable (number,body,status)
select number,body,'P'
from inserted
where flag = 0
end
Also notice your trigger is much simpler now.
Since those two statements are in one SQL batch, the trigger will (by design) only fire once.
Triggers don't fire once per row - they fire once per statement! So if you have an INSERT or UPDATE statement that affects more than one row, your trigger will have more than one row in the Inserted (and possibly Deleted) pseudo tables.
The way you wrote this trigger is really not taking into account that Inserted could contain multiple rows - what row do you select from the Inserted table if you're inserting 20 rows at once?
select #number = number, #body = body, #flag = flag from inserted
You need to change your trigger to take that into account!
I have a situation where I need to merge two product tables into one and need to keep both id fields. The Id field is the pk and an identity column. On insert I want to update the prodId to match the Id of the newly inserted row. This is what I have but I get an error saying that I cannot insert null into ProductId. What am I doing wrong?
ALTER TRIGGER SyncId
ON Product
FOR INSERT
AS
BEGIN
DECLARE #ID INT
SET #ID = (SELECT ID FROM Inserted)
UPDATE Product SET
ProdId = #ID
WHERE
Id = #ID
END
You could create an INSTEAD OF trigger for the INSERT. You would then have to recreate the actual INSERT logic. You might need a view of the table for the trigger to sit on though... I don't recall if an INSERT within the trigger will work or not and can't test it right now.
Also, your trigger as written will only work if a single row is being updated. You should always write your triggers to be able to handle multiple rows in the INSERTED and DELETED tables.
My database contains three tables called Object_Table, Data_Table and Link_Table. The link table just contains two columns, the identity of an object record and an identity of a data record.
I want to copy the data from DATA_TABLE where it is linked to one given object identity and insert corresponding records into Data_Table and Link_Table for a different given object identity.
I can do this by selecting into a table variable and the looping through doing two inserts for each iteration.
Is this the best way to do it?
Edit : I want to avoid a loop for two reason, the first is that I'm lazy and a loop/temp table requires more code, more code means more places to make a mistake and the second reason is a concern about performance.
I can copy all the data in one insert but how do get the link table to link to the new data records where each record has a new id?
In one statement: No.
In one transaction: Yes
BEGIN TRANSACTION
DECLARE #DataID int;
INSERT INTO DataTable (Column1 ...) VALUES (....);
SELECT #DataID = scope_identity();
INSERT INTO LinkTable VALUES (#ObjectID, #DataID);
COMMIT
The good news is that the above code is also guaranteed to be atomic, and can be sent to the server from a client application with one sql string in a single function call as if it were one statement. You could also apply a trigger to one table to get the effect of a single insert. However, it's ultimately still two statements and you probably don't want to run the trigger for every insert.
You still need two INSERT statements, but it sounds like you want to get the IDENTITY from the first insert and use it in the second, in which case, you might want to look into OUTPUT or OUTPUT INTO: http://msdn.microsoft.com/en-us/library/ms177564.aspx
The following sets up the situation I had, using table variables.
DECLARE #Object_Table TABLE
(
Id INT NOT NULL PRIMARY KEY
)
DECLARE #Link_Table TABLE
(
ObjectId INT NOT NULL,
DataId INT NOT NULL
)
DECLARE #Data_Table TABLE
(
Id INT NOT NULL Identity(1,1),
Data VARCHAR(50) NOT NULL
)
-- create two objects '1' and '2'
INSERT INTO #Object_Table (Id) VALUES (1)
INSERT INTO #Object_Table (Id) VALUES (2)
-- create some data
INSERT INTO #Data_Table (Data) VALUES ('Data One')
INSERT INTO #Data_Table (Data) VALUES ('Data Two')
-- link all data to first object
INSERT INTO #Link_Table (ObjectId, DataId)
SELECT Objects.Id, Data.Id
FROM #Object_Table AS Objects, #Data_Table AS Data
WHERE Objects.Id = 1
Thanks to another answer that pointed me towards the OUTPUT clause I can demonstrate a solution:
-- now I want to copy the data from from object 1 to object 2 without looping
INSERT INTO #Data_Table (Data)
OUTPUT 2, INSERTED.Id INTO #Link_Table (ObjectId, DataId)
SELECT Data.Data
FROM #Data_Table AS Data INNER JOIN #Link_Table AS Link ON Data.Id = Link.DataId
INNER JOIN #Object_Table AS Objects ON Link.ObjectId = Objects.Id
WHERE Objects.Id = 1
It turns out however that it is not that simple in real life because of the following error
the OUTPUT INTO clause cannot be on
either side of a (primary key, foreign
key) relationship
I can still OUTPUT INTO a temp table and then finish with normal insert. So I can avoid my loop but I cannot avoid the temp table.
I want to stress on using
SET XACT_ABORT ON;
for the MSSQL transaction with multiple sql statements.
See: https://msdn.microsoft.com/en-us/library/ms188792.aspx
They provide a very good example.
So, the final code should look like the following:
SET XACT_ABORT ON;
BEGIN TRANSACTION
DECLARE #DataID int;
INSERT INTO DataTable (Column1 ...) VALUES (....);
SELECT #DataID = scope_identity();
INSERT INTO LinkTable VALUES (#ObjectID, #DataID);
COMMIT
It sounds like the Link table captures the many:many relationship between the Object table and Data table.
My suggestion is to use a stored procedure to manage the transactions. When you want to insert to the Object or Data table perform your inserts, get the new IDs and insert them to the Link table.
This allows all of your logic to remain encapsulated in one easy to call sproc.
If you want the actions to be more or less atomic, I would make sure to wrap them in a transaction. That way you can be sure both happened or both didn't happen as needed.
You might create a View selecting the column names required by your insert statement, add an INSTEAD OF INSERT Trigger, and insert into this view.
Before being able to do a multitable insert in Oracle, you could use a trick involving an insert into a view that had an INSTEAD OF trigger defined on it to perform the inserts. Can this be done in SQL Server?
Insert can only operate on one table at a time. Multiple Inserts have to have multiple statements.
I don't know that you need to do the looping through a table variable - can't you just use a mass insert into one table, then the mass insert into the other?
By the way - I am guessing you mean copy the data from Object_Table; otherwise the question does not make sense.
//if you want to insert the same as first table
$qry = "INSERT INTO table (one, two, three) VALUES('$one','$two','$three')";
$result = #mysql_query($qry);
$qry2 = "INSERT INTO table2 (one,two, three) VVALUES('$one','$two','$three')";
$result = #mysql_query($qry2);
//or if you want to insert certain parts of table one
$qry = "INSERT INTO table (one, two, three) VALUES('$one','$two','$three')";
$result = #mysql_query($qry);
$qry2 = "INSERT INTO table2 (two) VALUES('$two')";
$result = #mysql_query($qry2);
//i know it looks too good to be right, but it works and you can keep adding query's just change the
"$qry"-number and number in #mysql_query($qry"")
I have 17 tables this has worked in.
-- ================================================
-- Template generated from Template Explorer using:
-- Create Procedure (New Menu).SQL
--
-- Use the Specify Values for Template Parameters
-- command (Ctrl-Shift-M) to fill in the parameter
-- values below.
--
-- This block of comments will not be included in
-- the definition of the procedure.
-- ================================================
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE InsetIntoTwoTable
(
#name nvarchar(50),
#Email nvarchar(50)
)
AS
BEGIN
SET NOCOUNT ON;
insert into dbo.info(name) values (#name)
insert into dbo.login(Email) values (#Email)
END
GO