SQL Server Triggers: Update - sql-server

Using Microsoft SQL Server, I am writing a SQL trigger for an update on a table and I am stuck. I am not very proficient in SQL, so it may be something basic that I am missing.
CREATE TRIGGER test
ON tableName
AFTER UPDATE
AS
BEGIN
DECLARE #variableA int
SELECT #variableA = variableA FROM DELETED
DECLARE #variableB int
SELECT #variableB = variableB FROM INSERTED;
f(#variableA <> #variableB )
BEGIN
//Do What I want
END
This works correctly, as it preforms the action when the two variables are different. However, I do not want to consider ALL records from tableName.
I wrote the following to get only the entries that I want.
WITH
table (variableID, variable)
AS
(
SELECT variableID, variable
FROM tableName
WHERE variable= 'value'
)
SELECT * FROM table
So what I want is to apply the trigger ONLY to the values that are found in the SELECT. Am I going about this the right way?

An UPDATE query isn't guaranteed to only impact a single row at a time, and your queries against INSERTED and DELETED need to reflect that to be on the safe side. Probably the easiest way to detect a changed value is to join the two trigger tables on the primary key.
CREATE TRIGGER test ON tableName
AFTER UPDATE AS
BEGIN
IF EXISTS (
SELECT * FROM INSERTED I
INNER JOIN DELETED D ON I.variableID = D.variableID
WHERE D.VariableA <> I.VariableB
/* AND further conditions */
) BEGIN
-- Perform your action
END
END
Where this snippet has /* AND further conditions */ would be a good place to insert the additional checks you want to do against the data before running your action.
For example, you can limit your action to updates where variable was 'value' before the update...
AND D.variable = 'value'
or where variable is set to 'value' by the update...
AND I.variable = 'value'

Related

SQL Server a trigger to work on multiple rows updated

I have a problem with trigger which I use for procedure implementation. I use inserted table to collect all rows which user updated and procure procedure. But procedure repeat for ech row separately. How to handle this?
my trigger:
ALTER TRIGGER [dbo].[FormUpdate]
ON [dbo].[FORM]
For UPDATE
AS
BEGIN
SET NOCOUNT ON;
select * into #inserted from (
SELECT i.* from FORM gw
inner join inserted i on gw.FORMID = i.FORMID) t
WHERE t.PREPARING <> 0
IF (SELECT COUNT(*) FROM #inserted) > 0
BEGIN
UPDATE GW
SET PREPARING = 0
FROM FORM GW
INNER JOIN #inserted on GW.FORMID = #inserted.FORMID
EXEC dbo.PREPARING_OF_THE_FORM
END
END
I may be way off but i think i am pretty close.
As it looks right now the Form table is updated and then the stored procedure runs. I am guessing the stored procedure runs some work on the form table. If you want to run it on every row it looks like (i hate to say it) you need a CURSOR.
You will need to create a cursor from your first select something like
select * into #inserted from (
SELECT i.* from FORM gw
inner join inserted i on gw.FORMID = i.FORMID) t
WHERE t.PREPARING <> 0
DECLARE CURSOR inserted_Cursor
FOR
SELECT *
FROM #inserted
OPEN inserted_cursor
FETCH NEXT FROM inserted_cursor
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE GW
SET PREPARING = 0
FROM FORM GW
INNER JOIN #inserted on GW.FORMID = #inserted.FORMID
EXEC dbo.PREPARING_OF_THE_FORM
FETCH NEXT FROM inserted_cursor
END
CLOSE inserted_cursor
DEALLOCATE inserted_Cursor
I haven't had a chance to test this so it may take some fiddling to get it to run. I am not sure what the stored procedure is doing but you will need to make sure it can handle one record at a time. It may be best to not use the stored procedure and just write out the code to ensure it can handle the one row at a time.
Also keep in mind there are better ways to do this but with your specific application and without knowing the architecture this is what i think the best solution for you.

SQL Server Triggers: How can I obtain a value used in the query string in my trigger's where clause?

I need to create a trigger in SQL Server which is conditionally fired when a user attempts to delete a row in the table.
I have 2 tables in the database Airlines:
Passenger - it has information on passengers. I need to create trigger on this table, obviously
Record - it records the flight(s) a passenger has been on
My trigger should work as follows:
If a passenger has never been on a flight, it should be deleted if attempted.
But if he/she has NOT been on a flight, it should restrict the action and print the number of times he has been on any flight.
The only thing (I hope) I am struggling with is:
How would I specify any WHERE clause inside a query in the trigger if I do not know which particular passenger I need to look for until a user attempts to delete it?
So, long story short: is there any way to obtain a value passed in a query's WHERE clause to be used in a trigger?
Thank you very much for your time!
Here is my code:
ALTER TRIGGER Restrict_Delete
ON Records
INSTEAD OF DELETE
AS
BEGIN
DECLARE #r_count INT
SET #r_count = (SELECT DISTINCT COUNT(*)
FROM Passenger P, Records R
WHERE P.passenger_id = R.passenger_id
AND P.passenger_id = ???)
IF #r_count > 0
BEGIN
ROLLBACK TRAN
PRINT ('Permission denied. ' + CAST (#r_count AS Varchar(3)) + ' record(s) exist.')
END
ELSE
PRINT 'No records exist. Record deleted!'
END
How do I determine the passenger_id in my query?
You can use the deleted and inserted tables!
These are special tables that exist in triggers and contain records copied from the actual tables. When you change a row in a table, a copy of the old row goes into the deleted table, and a copy of the new row goes into the inserted table. Since you're just deleting, you only need to use the deleted table.
Here's how the SQL could look inside your trigger:
DECLARE #r_count INT
SET #r_count = Count(*)
FROM Records R -- You don't actually need the Passenger table for this.
WHERE r.Passenger_id IN (
select d.Passenger_id
from Deleted d
)
IF #r_count > 0
BEGIN
Rollback Tran
PRINT ('Permission denied. ' + CAST (#r_count AS Varchar(3)) + ' record(s) exist.')
END
ELSE
PRINT 'No records exist. Record deleted!'
Something you need to be aware of, though: a trigger is called once per statement, not once per record. So if you delete two passengers with one DELETE statement, you'll get only one trigger call. The logic you had (and I adapted) will check for any record that was deleted by that DELETE statement. You could get quite a large number for #r_count if you're doing a bulk delete!
If you need to code around that, try to avoid using a cursor actually in your trigger: it will make deletes very slow.
Also, be aware that the PRINT statement will appear in SSMS and can be retrieved in ADO.NET with a bit of fiddling around, but doesn't appear in traces or get returned as part of a recordset. If you need to log this failure, you're going to need to write to a database table.

How to run SQL trigger

I am completely new to SQL Server 2008 and I wrote a trigger and would like to be executed only of hassubproduct and spdisplaytype columns are updated or inserted and if they have a value and are not empty.
Any help is appreciated.
CREATE TRIGGER [dbo].[hassubproductcheck]
ON [dbo].[products]
WITH EXECUTE AS CALLER
FOR INSERT, UPDATE
AS
BEGIN
UPDATE products
SET hassubproduct = LTRIM(RTRIM(hassubproduct))
UPDATE products
SET spdisplaytype = LTRIM(RTRIM(spdisplaytype))
END
GO
Something along these lines is probably more like what you want.
CREATE TRIGGER [dbo].[hassubproductcheck] ON [dbo].[products]
WITH EXECUTE AS CALLER FOR INSERT, UPDATE
AS BEGIN
Update p
set hassubproduct = LTRIM(RTRIM(i.hassubproduct))
, spdisplaytype = LTRIM(RTRIM(i.spdisplaytype))
from Products p
join inserted i on i.PrimaryKey = p.PrimaryKey
where i.hassubproduct > ''
OR i.spdisplaytype > ''
END
You have to use the below code to define the trigger. This will help for updated. The same way we need to create one for inserted as well.
CREATE TRIGGER [dbo].[hassubproductcheck] ON [dbo].products]
AFTER UPDATE
AS
INSERT INTO DBO.SAMPLE_TRIGGER
SELECT hassubproduct, 'UPDATE(PREVIOUS)' [TABLE-UPDATE] FROM DELETED
GO
CREATE TRIGGER [dbo].[hassubproductcheck] ON [dbo].products]
AFTER UPDATE
AS
INSERT INTO DBO.SAMPLE_TRIGGER
SELECT hassubproduct, 'UPDATE (LATEST)' [TABLE-UPDATE] FROM DELETED
GO

If exists UPDATE else INSERT for each row of a table

I have a table named tableFrom want to insert into a table named tableTo. The insert worked well, but if I insert the same values again, I have a duplicate key error. So I want to update only the rows already existing. I know the command ON DUPLICATE with MySQL, unfortunately missing in SQL Server.
If I want to only check for only one precise row, it is easy:
IF EXISTS PK = #PK
But I am trying to do it for a whole table, and I don't know if it is possible. I thought of cheking each row with cursor, I am new to SQL.
Here is what I came up with:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
IF EXISTS (
SELECT
1
FROM
tableFrom F,
tableTo T
WHERE
T.product = F._product
)
BEGIN
UPDATE
tableTo
SET
T.something = F.something
FROM
tableTo T
INNER JOIN
tableFrom F
ON
T.product = F._product
END
ELSE
BEGIN
INSERT INTO tableTo
(product,
something)
SELECT
F._product,
F.something
FROM
tableFrom F
END
COMMIT TRANSACTION
The UPDATE part is working fine, but no INSERT done.
EDIT1:
Tried this code:
MERGE tableTo AS T
USING tableFrom AS S
ON (T.product= S._product)
WHEN NOT MATCHED BY TARGET
THEN INSERT(product, something) VALUES(S._product, S.something)
WHEN MATCHED
THEN UPDATE SET T.Something= S.Something
Have the following error: "Incorrect syntax near 'MERGE'. You may need to set the compatibility level of the current database to a higher value to enable this feature. See help for the SET COMPATIBILITY_LEVEL option of ALTER DATABASE."
EDIT2:
I googled the above error message and it appeared to be due to a missing semi-colon at the end of the very last line before the MERGE statement. The MERGE command is working perfect!
It's not missing. SQL Server implements the standard MERGE statement which allows you to specify what happens when a match occurs or not between a source and a target. Check the documentation for examples.
Matches are made using a condition that can involve many columns. MERGE allows you to execute an INSERT, UPDATE or DELETE in the following cases:
A match is found based on the condition
A match occurs only in the source
A match occurs only in the target
This way you can update existing rows, insert rows that exist only in the source, delete rows that appear only in the target.
In your case, you could do something like:
MERGE tableTo AS T
USING tableFrom AS S
ON (T.product= S._product)
WHEN NOT MATCHED BY TARGET
THEN INSERT(product, something) VALUES(S._product, S.something)
WHEN MATCHED
THEN UPDATE SET T.Something= S.Something
OUTPUT $action, Inserted.*, Deleted.*;
This statement will insert or update rows as needed and return the values that were inserted or overwritten with the OUTPUT clause.
Use the Merge operation.
From the MSDN documentation:
Performs insert, update, or delete operations on a target table based
on the results of a join with a source table. For example, you can
synchronize two tables by inserting, updating, or deleting rows in one
table based on differences found in the other table.

MSSQL Trigger - Issue

I have a SQL trigger on a table, which will fire after insert, update and delete.
I insert all the affected records in a separate physical table with codes defining the state of update. Following code snippet is the trigger defined.
CREATE TRIGGER [dbo].[DATA_CACHE]
ON [dbo].[DATA_USAGE]
for Insert,Update,Delete
AS
BEGIN
if(select COUNT(*) from inserted)>0
begin
if (select COUNT(*) from deleted)>0
BEGIN
--update
INSERT INTO CACHE_UPDATE_TABLE (CODE, ID, DATE, COUNT)
SELECT 2, ins.ID, ins.DATE, ins.COUNT
from inserted ins
END
else
begin
-- insert
INSERT INTO CACHE_UPDATE_TABLE (CODE, ID, DATE, COUNT)
SELECT 1, ins.ID, ins.DATE, ins.COUNT
from inserted ins
end
END
else
BEGIN
-- delete
INSERT INTO CACHE_UPDATE_TABLE (CODE, ID, DATE, COUNT)
SELECT 3, del.ID, del.DATE, del.COUNT
from deleted del
end
END
SELECT * FROM CACHE_UPDATE_TABLE
As you can see in the above trigger i had added an additional statement after the trigger by MISTAKE, selecting all values from the target table. This statement was after the defined trigger, however when i tried to alter the trigger, by right clicking on trigger and selecting modify, it also showed me the select statement after the end block of trigger.
Does this mean, every time the trigger is fired this select statement executes ? this is my first question (Question A) - May be a silly one, but i am a little confused about this.
My second question is (Question B) I encounter locking issue on the CACHE_UPDATE_TABLE, could this be the reason for locking? Also there is a SQL job which runs every one minute to check the CACHE_UPDATE_TABLE table, and then i perform some operation(linked server related) and delete these records from CACHE_UPDATE_TABLE after i am done. Locking Issue could be because of this?? and if so, how do i counter it?
My third question is (Question C) Is this the best way to do this operation using triggers or can i do it some other way? Is the trigger defined proper?
-Any help will be appreciated... Thanks.
You've got a lot of different questions in there which is probably why you've not received any answers, but I'll cover what I can.
A) That's quite an interesting question actually. I would have assumed that it would do nothing - It'd be executed when you create the trigger but then wouldn't be part of the trigger - however I've noticed odd behaviour with this before so I tested with a simple stored procedure:
CREATE PROCEDURE dbo.test ( #i INT ) AS
BEGIN
SELECT #i
END;
SELECT 'hi'
GO
Executing the stored procedure causes the SELECT 'hi' to fire as well as the SELECT #i. I still don't have an answer for your question, but I would definitely make sure not to have any stray SQL outside the trigger when you create it for this reason alone.
I've just investigated this a little more and apparently the end of the stored procedure is wherever the first GO is after the procedure (which SQL Server automatically adds to the end if you don't use one). So you could define your whole procedure after the END - you can still use the parameters too.
This seems to be because the BEGIN and END aren't a required part of the stored procedure definition - they're not actually indicating the begin and end of the stored procedure, they're just an unrelated BEGIN...END block like you might put after and IF statement. You can have as many BEGIN...END blocks as you like in the procedure definition, or none at all.
C) I would definitely change your trigger. You've massively complicated it by combining the 3 triggers without reusing any code. The only reason to combine INSERT,UPDATE and DELETE triggers is so that you don't have to duplicate code. You should either:
Have 3 separate triggers, each containing only the relevant INSERT - that way you remove all of the conditional logic.
Keep them together but work out only the CODE using some conditional logic and have only 1 INSERT statement.
I'd be tempted to go with the 3 separate triggers, or at least an separate out the delete trigger, and then use CASE del.ID IS NULL THEN 1 ELSE 2 END for the CODE on the INSERT/UPDATE trigger. But you could combine them with (untested):
INSERT INTO CACHE_UPDATE_TABLE (CODE, ID, DATE, COUNT)
SELECT CASE WHEN del.ID IS NULL THEN 1
WHEN ins.ID IS NULL THEN 3
ELSE 2 END
,ISNULL(ins.ID, del.ID)
,ISNULL(ins.DATE, del.DATE)
,ISNULL(ins.COUNT, del.COUNT)
FROM deleted del
FULL OUTER JOIN inserted ins ON del.ID = ins.ID
Just remove that
SELECT * FROM CACHE_UPDATE_TABLE

Resources