I have the following trigger:
ALTER TRIGGER [Staging].[tr_UriData_ForInsert]
ON [Staging].[UriData]
FOR INSERT
AS
BEGIN
DECLARE #_Serial NVARCHAR(50)
DECLARE #_Count AS INT
IF ##ROWCOUNT = 0
RETURN
SET NOCOUNT ON;
IF EXISTS(SELECT * FROM inserted)
BEGIN
SELECT #_Count = COUNT(Id) FROM inserted
SELECT #_Serial = SerialNumber FROM inserted
INSERT INTO [Staging].[DataLog]
VALUES (CURRENT_TIMESTAMP, #_Serial + ': Data Insert --> Rows inserted: ' + #_Count, 'New data has been received')
END
END
The table receives multiple rows at once. I want to be able to add one row in the log table to tell me the insert has happened.
It works great with one row being inserted, but with multiple rows, the trigger doesn't fire. I have read other items on here and it is quite clear that you shouldn't use ROW_NUMBER().
In summary: I want to update my log table when a multiple row insert happens in another table called UriData.
The data is inserted from C# using the following:
using (var sqlBulk = new SqlBulkCopy(conn, SqlBulkCopyOptions.Default, transaction))
{
sqlBulk.DestinationTableName = tableName;
try
{
sqlBulk.WriteToServer(dt);
}
catch(SqlException sqlEx)
{
transaction.Rollback();
var msg = sqlEx.Message;
return false;
}
finally {
transaction.Commit();
conn.Close();
}
}
I don't want to know what is being inserted, but when it has happened, so I can run a set of SPROCS to clean and pivot the data.
TIA
The problem is your trigger assumes that only one row will be updated. A scalar variable can only have 1 value. So, for example, the statement SELECT #_Serial = SerialNumber FROM inserted will set #_Serial with the last value returned from the object inserted.
Treat your data as what it is, a dataset. This is untested, however, I suspect this gives you the result you want:
ALTER TRIGGER [Staging].[tr_UriData_ForInsert]
ON [Staging].[UriData]
FOR INSERT
AS
BEGIN
--No need for a ROWCOUNT. If there are no rows, then nothing was inserted, and this trigger won't happen.
INSERT INTO [Staging].[DataLog] ({COLUMNS LIST})
SELECT CURRENT_TIMESTAMP,
SerialNumber + ': Data Insert --> Rows inserted: ' +
CONVERT(varchar(10),COUNT(SerialNumber) OVER (PARTITION BY SerialNumber)), --COUNT returns an INT, so this statement would have failed with a conversion error too
'New data has been received'
FROM inserted;
END
Please note my comments or sections in braces ({}).
Edit: Sean, who has since deleted his answer, used GROUP BY. I copied what exact method you had, however, GROUP BY might well be the clause you want, rather than OVER.
So after a lot of digging and arguing, my hosting company told me that they have disabled bulk inserts of any kind, without bothering to notify their customers.
Related
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.
I can detect duplicate records, but when I'm inserting new data it will detect it as a duplicate record even if doesn't already exist.
Here is my code:
ALTER TRIGGER [dbo].[SDPRawInventory_Dup_Trigger]
ON [dbo].[SDPRawInventory]
AFTER INSERT
AS
DECLARE #Year float,
#Month float,
#SDPGroup nvarchar(255);
SELECT
#Year = i.InvYear, #Month = i.InvMonth, #SDPGroup = i.SDPGroup
FROM inserted i;
IF (SELECT COUNT(*) FROM SDPRawInventory A
WHERE A.InvYear = #Year
AND A.InvMonth = #Month
AND A.SDPGroup = #SDPGroup) >= 1
BEGIN
RAISERROR ('Duplicate data', 16, 1)
ROLLBACK;
END
ELSE
BEGIN
INSERT INTO SDPRawInventory
SELECT * FROM inserted;
END
This is the table
And to clarify there is no primary key nor unique identifier.
If you are unable to put a constraint in place, then you need to handle the fact that Inserted may have multiple records. And because its an after insert trigger, you don't need to do anything if no duplicates are found because the records are already inserted.
ALTER TRIGGER [dbo].[SDPRawInventory_Dup_Trigger]
ON [dbo].[SDPRawInventory]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (
SELECT 1
FROM dbo.SDPRawInventory S
INNER JOIN Inserted I ON
-- Test for a duplicate
S.InvYear = I.InvYear
AND S.InvMonth = I.InvMonth
AND S.SDPGroup = I.SDPGroup
-- But ensure the duplicate is a *different* record - assumes a unique ID
AND S.ID <> I.ID
)
BEGIN
THROW 51000, 'Duplicate data.', 1;
END;
END;
Note the simplified and modern error handling.
EDIT: And if you have no unique key, and no permission to add one, then you need an instead of trigger to only insert non-duplicates e.g.
ALTER TRIGGER [dbo].[SDPRawInventory_Dup_Trigger]
ON [dbo].[SDPRawInventory]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
-- Reject the entire insert if a single duplicate exists
-- Note if multiple records are inserted, some of which are duplicates and some of which aren't, they all get rejected
IF EXISTS (
SELECT 1
FROM dbo.SDPRawInventory S
INNER JOIN Inserted I ON
-- Test for a duplicate
A.InvYear = I.InvYear
AND A.InvMonth = I.InvMonth
AND A.SDPGroup = I.#SDPGroup
)
-- Test that Inserted itself doesn't contain duplicates
OR EXISTS (SELECT 1 FROM Inserted GROUP BY InvYear, InvMonth, SDPGroup HAVING COUNT(*) > 1)
BEGIN
THROW 51000, 'Duplicate data.', 1;
END;
INSERT INTO dbo.SDPRawInventory (SDP_SKU_DESC, WholeQty, InvYear, InvMonth, SDPGroup, invUOM, LooseQty)
SELECT SDP_SKU_DESC, WholeQty, InvYear, InvMonth, SDPGroup, invUOM, LooseQty
FROM Inserted I
WHERE NOT EXISTS (
SELECT 1
FROM dbo.SDPRawInventory S
-- Test for a duplicate
WHERE S.InvYear = I.InvYear
AND S.InvMonth = I.InvMonth
AND S.SDPGroup = I.SDPGroup
);
END;
Note: This doesn't do anything to handle existing duplicates.
This trigger is executed after the new records were inserted, so it will at least find the original records in the SELECT COUNT statement. Changing >= 1 into >= 2 can only partially fix this when inserting is guaranteed to occur one record as a time. Moreover, it will still fail when there were already multiple duplicated of the newly inserted record in the database before the insert.
You need to exclude the latest inserted records from the COUNT. But a better idea would probably be to add a UNIQUE constraint for preventing duplicates, so no trigger would be necessary.
If adding a constraint is not possible yet, you should initiate a clean-up process to eliminate the existing duplicates beforehand. Everything else is looks pretty flawed to me, since it is unlikely the current approach will ever bring the table into a state with no duplicates.
You are creating the infinite loop. You just have to remove the insertion part from your trigger.
ELSE
BEGIN
INSERT INTO SDPRawInventory
SELECT * FROM inserted;
END
This should not be in the trigger as trigger is called as part of insertion. you should not write actual insertion in to table in trigger.
I am copying data from Table1 to Table2 table using sql bulk copy. I have applied trigger on Table2, but my trigger is not firing on every row. Here is my trigger and sqlbulkcopy function.
SqlConnection dstConn = new SqlConnection(ConfigurationManager.ConnectionStrings["Destination"].ConnectionString);
string destination = dstConn.ConnectionString;
//Get data from Source in our case T1
DataTable dataTable = new Utility().GetTableData("Select * From [db_sfp_ems].[dbo].[tbl_current_data_new] where [start_date]>'" + calculate_daily_Time + "' and status=0" , source);
SqlBulkCopy bulkCopy = new SqlBulkCopy(source, SqlBulkCopyOptions.FireTriggers)
{
//Add table name of source
DestinationTableName = "tbl_current_data",
BatchSize = 100000,
BulkCopyTimeout = 360
};
bulkCopy.WriteToServer(dataTable);
//MessageBox.Show("Data Transfer Succesfull.");
dstConn.Close();
------Trigger-----
ALTER TRIGGER [dbo].[trgAfterInsert] ON [dbo].[tbl_current_data]
AFTER INSERT
AS
BEGIN
declare #intime datetime
declare #sdp_id numeric
declare #value numeric(9,2)
SELECT #intime= DATEADD(SECOND, -DATEPART(SECOND, start_date), start_date) FROM INSERTED
SELECT #sdp_id= sdp_id FROM INSERTED
SELECT #value= value FROM INSERTED
INSERT INTO Table3(sdp_id,value,start_date)
VALUES
(
#sdp_id,#value,#intime
)
A trigger is fired after an insert, whether that insert concerns 0, 1 or multiple records makes no difference to the trigger. So, even though you are inserting a whole bunch of records, the trigger is only fired once. This is by design, and not specific for BULK_INSERT; this is true for every kind of insert. This also means that the inserted pseudo table can hold 0, 1 or multiple records. This is a common pitfall. Be sure to write your trigger in such a way it can handle multiple records. For example: SELECT #sdp_id= sdp_id FROM INSERTED won't work as expected if inserted holds multiple records. The variable will be set, but you cannot know what value (from which inserted record) it's going to hold.
This is all part of the set based philosophy of SQL, it is best not to try and break that philosophy by using loops or other RBAR methods. Stay in the set mindset.
Your trigger is simply broken. In SQL Server, triggers handle multiple rows at one time. Assuming that inserted has one row is fatal error -- and I wish it caused a syntax error.
I think this is the code you want:
ALTER TRIGGER [dbo].[trgAfterInsert] ON [dbo].[tbl_current_data]
AFTER INSERT
AS
BEGIN
INSERT INTO Table3 (sdp_id, value, start_date)
SELECT sdp_id, value,
DATEADD(SECOND, -DATEPART(SECOND, start_date), start_date)
FROM inserted i;
END;
Apart from being correct, another advantage is that the code is simpler to write.
Note: You are setting the "seconds" part to 0. However -- depending on the type -- start_date could have fractional seconds that remain. If that is an issue, ask another question.
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 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