I have a trigger that check data before insert to another table
IF NOT EXISTS (SELECT *
FROM inserted,
Clients
WHERE Inserted.Account = Clients.Account)
BEGIN
INSERT INTO Clients(GUID, Account, ....)
SELECT GUID, Account
FROM inserted
END
it's working fine if one row updated
but if use batch update its not working.
for example Update Table1 set Number = Number where account <> ''
If the second table (Clients) is not empty, nothing added
if it's empty its work well
Rather than doing a check then insert, why not write it as a single INSERT statement:
Insert into Clients (GUID , Account .....)
Select i.GUID , i.Account
from
inserted i
left join
Clients c
on
i.Account = c.Account
where
c.Account is null
The NULL check in the WHERE clause can be for any column in Clients which isn't nullable.
Even if you keep the EXISTS check in, you still need something like the above, because the EXISTS check is asserting something about all of the rows in inserted - when it may consist of some rows for which the assertion is true, and some rows for which it is false.
Related
Hej,
Update
Thank you guys for your answers and hints. I understand that my first attempt was wrong - so maybe the better approach is to describe my problem in words rather than trying a shitty trigger.
Table A is a list of all clients. For earch client exists multiple orders (next to other not needed information in that table):
CLIENT
ORDER
OPTIONAL
A
1
NO
A
2
YES
A
3
NO
B
16818
YES
B
342
YES
I need to insert all OPTIONAL=NO orders into table B in an automatic bulk process. It is possible that an order is changed from OPTIONAL=NO to OPTIONAL=YES and therefore i need the solutions for not only inserts but updates.
Thank you very much!
Best practice for triggers:
Don't create them unless you have no other option.
It's usually best to separate insert and update triggers, sometimes they can be combined.
NOCOUNT stops spurious messages going back to the client, XACT_ABORT means any error automatically rolls back the transaction.
Check if there are any relevant rows, if not bail out early.
Also check if a column is present in the update statement using the UPDATE() function if relevant (this doesn't mean the row has actually changed)
In an UPDATE trigger, make sure you compare inserted and deleted rows for actual changes.
Most important: be aware that inserted and deleted may contain multiple rows.
CREATE TRIGGER Trg_TableA_OPTIONAL_ins
ON TableA
AFTER INSERT
AS
SET NOCOUNT, XACT_ABORT ON;
IF (NOT EXISTS (SELECT 1 FROM inserted WHERE OPTIONAL = 'NO'))
RETURN;
INSERT TableB (CLIENT, [ORDER])
SELECT CLIENT, [ORDER]
FROM inserted
WHERE OPTIONAL = 'NO';
GO
CREATE TRIGGER Trg_TableA_OPTIONAL_upd
ON TableA
AFTER UPDATE
AS
SET NOCOUNT, XACT_ABORT ON;
IF (NOT EXISTS (SELECT 1 FROM inserted))
RETURN;
INSERT TableB (CLIENT, [ORDER])
SELECT CLIENT, [ORDER]
FROM (
SELECT CLIENT, [ORDER], OPTIONAL
FROM inserted
WHERE OPTIONAL = 'NO'
EXCEPT
SELECT CLIENT, [ORDER], OPTIONAL
FROM deleted
) i
EXCEPT
SELECT CLIENT, [ORDER]
FROM TableB;
DELETE FROM b
FROM TableB b
JOIN (
SELECT CLIENT, [ORDER], OPTIONAL
FROM inserted
WHERE OPTIONAL = 'YES'
EXCEPT
SELECT CLIENT, [ORDER], OPTIONAL
FROM deleted
) i
ON TableB.CLIENT = Source.CLIENT AND TABLEB.[ORDER] = Source.[ORDER];
GO
Update: Just to eliminate one possible suggestion, I saved the first query as a view and substituted it in the trigger. No change in behavior has been observed. I have updated the code below to reflect the new arrangement.
I have just started working with SQL Server triggers and have had some success. The current bit that I'm working on has me confused.
I am working with two tables. One is an order header and the other contains the line items. The trigger is watching for a new row to be inserted in the line item table and, if the item added meets certain criteria, update a value in the order header table. When I break my query down and run it separately, I get what I expect. A list of orders that contain the items I am watching for.
--QUERY WORKS AS EXPECTED
SELECT ord_no, cmt_cd_1
FROM OrderHeader
WHERE ord_no IN
(SELECT DISTINCT OrderHeader.ord_no
FROM OrderLine INNER JOIN OrderHeader
ON OrderLine.ord_no = OrderHeader.ord_no
WHERE (OrderLine.item_no LIKE 'A%') OR
(OrderLine.item_no LIKE 'B%') OR
(OrderLine.item_no LIKE 'C30%'))
However, when I try to use this in a trigger, it doesn't work.
--TRIGGER DOES NOT UPDATE ANY RECORDS
CREATE TRIGGER add_cmt_cd
ON [OrderLine]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
UPDATE OrderHeader
SET cmt_cd_1 = 'O'
FROM OrderHeader
INNER JOIN inserted ON OrderHeader.ord_no = inserted.ord_no
WHERE (OrderHeader.ord_no IN (SELECT OrderListView.ord_no
FROM OrderListView))
END
I have verified that the header record is created first and does exist before the line item records are created. I have another trigger on this table that works fine, though the value it updates is in the same table as the trigger.
--TRIGGER WORKS AS DESIGNED
CREATE TRIGGER add_line_cmt
ON [OrderLine]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
UPDATE OrderLine
SET cmt_1 = 'Comment 1',
cmt_2 = 'Comment 2'
FROM OrderLine
INNER JOIN inserted ON inserted.ord_no = OrderLine.ord_no
WHERE (OrderLine.item_no IN (SELECT CAST(item_no AS CHAR) AS item
FROM ItemTable
WHERE (group = 3)))
END
I've been banging my head against the desk for 3 hours and I know it's something simple I've missed. I'm hoping some other sets of eyes will spot it before I go nuts.
I have a tables job_costcodes(id, cost_code_no, dept_id) and cost_codes(code_code_no, dept_id).
I am trying to make it so if job_costcodes.cost_code_no is modified, job_costcodes.dept_id is filled with the appropriate one from the cost_codes table, based on a matching code_code_no.
So referring to the tables below, if the top row in job_costcodes is changed to 10, the dept_id should change to 1212. Or 20 to 1313, etc.
I am not sure exactly how the syntax works... here is what I have so far.
UPDATE: updated code.. i think it works now.
create trigger update_test on dbo.job_costcodes
for update, insert
as
begin
set nocount on
update dbo.job_costcodes
set dept_id = (select CASE WHEN COUNT(1) > 0 THEN MIN(dbo.cost_codes.dept_id) ELSE NULL END as Expr1
FROM inserted INNER JOIN
dbo.cost_codes ON dbo.cost_codes.cost_code_no = inserted.cost_code_no)
from inserted as i
inner join dbo.[job_costcodes] on dbo.[job_costcodes].id = i.id
end
Treating your question as academic, start off by looking up the CREATE TRIGGER command in TSQL to get a solid understanding of the virtual tables inserted and deleted.
Then here is what I would do, in pseudo-cod-ish descriptive terms:
In your trigger, simply UPDATE job_costcodes and set the value of dept_id to the corresponding dept_id in cost_codes by JOINing to cost_codes and inserted in the FROM clause of the UPDATE.
There is no need to verify that the cost_code_no changed when doing this, the result will be the same, but if you feel you must do this, then look at the IF UPDATE() function in TSQL. You can then compare the value of cost_code_no in inserted vs deleted to know if it changed at all.
I'm migrating an Access database to a SQL Server 2014 Express back end and the application as it is is designed to put empty strings in some columns in some of the tables instead of NULL's (Access's behavior on attached forms).
It turns out I can't have the table not allow nulls in these columns because when attached to a bound form via ODBC attached tables Access explicitly is trying to insert NULL values when someone simply deletes the contents of a column, even if I have a default value defined on the table.
I want to say up front that I will be fixing this to handle NULLs properly, but for 'right now', my priority is to just get the back end converted to SQL operating the same as it was in Access, so I just want a trigger to change NULL values on a few fields to empty strings until such a time when I can look at all the application logic which is expecting empty strings in these fields right now and change it to handle NULLs.
I came up with the following:
CREATE TRIGGER TABLE1_ReplaceNulls
ON TABLE1
AFTER INSERT, UPDATE
AS
IF UPDATE(FIELDWNULL1)
BEGIN
UPDATE TABLE1
SET FIELDWNULL1 = ''
FROM inserted I
INNER JOIN TABLE1 ON I.PKFIELD = TABLE1.PKFIELD
WHERE I.FIELDWNULL1 IS NULL;
END;
This works fine for a single column. How best do I do this for multiple columns in the same table? I have one table with 4 columns that all could contain NULLS, but I want empty strings in place of them.
Should I do a separate IF block for each column that could contain the NULL or just handle it all at once? Of course, if I handle it all at once I would have to consider some of the columns might have legit values, but if I do separate statements then it could essentially run 4 updates after a column is inserted. Maybe it doesn't matter as this is all temporary, but just curious on other more experienced thoughts.
Using below update statement,update all four column in one trans. This code is not tested.
UPDATE TABLE1
SET FIELDWNULL1=iif(FIELDWNULL1 is null,'',FIELDWNULL1),
FIELDWNULL2=iif(FIELDWNULL2 is null,'',FIELDWNULL2),
FIELDWNULL3=iif(FIELDWNULL3 is null,'',FIELDWNULL3),
FIELDWNULL4=iif(FIELDWNULL4 is null,'',FIELDWNULL4)
FROM inserted I INNER JOIN TABLE1
ON I.PKFIELD = TABLE1.PKFIELD
New trigger code: With IIF statement
CREATE TRIGGER TABLE1_ReplaceNulls ON TABLE1
AFTER INSERT, UPDATE
AS
--IF UPDATE(FIELDWNULL1)
BEGIN
UPDATE TABLE1
SET FIELDWNULL1=iif(FIELDWNULL1 is null,'',FIELDWNULL1),
FIELDWNULL2=iif(FIELDWNULL2 is null,'',FIELDWNULL2),
FIELDWNULL3=iif(FIELDWNULL3 is null,'',FIELDWNULL3),
FIELDWNULL4=iif(FIELDWNULL4 is null,'',FIELDWNULL4)
FROM inserted I INNER JOIN TABLE1
ON I.PKFIELD = TABLE1.PKFIELD
--WHERE I.FIELDWNULL1 IS NULL;
END;
With ISNULL() function
CREATE TRIGGER TABLE1_ReplaceNulls ON TABLE1
AFTER INSERT, UPDATE
AS
--IF UPDATE(FIELDWNULL1)
BEGIN
UPDATE TABLE1
SET FIELDWNULL1=ISNULL(FIELDWNULL1,''),
FIELDWNULL2=ISNULL(FIELDWNULL2,''),
FIELDWNULL3=ISNULL(FIELDWNULL3,''),
FIELDWNULL4=ISNULL(FIELDWNULL4,'')
FROM inserted I INNER JOIN TABLE1
ON I.PKFIELD = TABLE1.PKFIELD
--WHERE I.FIELDWNULL1 IS NULL;
END;
I'm trying to create a simple trigger using TSQL (or SQL Server 2008). The problem is: my current trigger is updating the entire table. This was fine for a while, but now the table has more than 20k rows. So I want a trigger that only updates the rows that are being inserted.
Here's my current simple trigger:
CREATE TRIGGER trig_MyPplUpdate
ON [Persons]
FOR INSERT
AS
Begin
Update Persons
set MyFile = NULL
where Len(MyFile) < 60
End
I think I'll have to use either the "inserted" table or the row_number function ordered by the primary key. Any ideas?
If it is necessary to use a trigger here at all I would use an INSTEAD OF trigger to adjust the values pre-insert and avoid the need to JOIN back onto the base table and Update them afterwards.
CREATE TRIGGER trig_MyPplUpdate
ON [Persons]
INSTEAD OF INSERT
AS
BEGIN
INSERT INTO Persons
SELECT foo,
bar,
CASE
WHEN Len(MyFile) >= 60 THEN MyFile
END
FROM Inserted
END
You need to join the Inserted pseudo table in your UPDATE statement. Always be aware that SQL Server fires the trigger once per statement and that statement can very well modify/insert multiple rows at once - so your Inserted table quite likely will contain more than one row - just need to keep that in mind when you write your trigger.
Try something like this:
CREATE TRIGGER trig_MyPplUpdate
ON [Persons]
FOR INSERT
AS
UPDATE dbo.Persons
SET MyFile = NULL
WHERE Len(MyFile) < 60
AND PersonID IN (SELECT DISTINCT PersonID FROM Inserted)
or use whatever unique column (your primary key) you have to get exactly those rows that have been inserted.