How do I get results of SQL data change statement with triggers updates applied? - sql

I have a requirement where I need to get the immediate result of an update statement. I saw that I can do that by using the SQL data-change-statement modifiers. However, I'm not being able to get the final result after applying all associated triggers. For example, let's say I have the following query:
SELECT empno, salary FROM FINAL TABLE
(UPDATE employee SET salary = salary * 1.10 WHERE job = 'CLERK')
And let's suppose I have a trigger that does the following:
CREATE TRIGGER EXTRA_PAY_RISE AFTER UPDATE ON employee
REFERENCING OLD AS oldrow
NEW AS newrow
FOR EACH ROW MODE DB2SQL
WHEN (newrow.dept = 'sales')
UPDATE employee SET salary = salary * 1.01 WHERE name = newrow.name;
How can I get the result from the first select statement containing the updates applied by all of the associated triggers (if that's possible)?

Use BEFORE UPDATE trigger and either NEW TABLE (in any case) or FINAL TABLE (if you don't have AFTER UPDATE triggers).
If you can't use BEFORE trigger to implement your update logic, then you can't use a data-change-statement to achieve your goal.

Related

Teradata Trigger Syntax (referencing similar rows for conditional insert)

I have a TeraData table that looks something like this:
Name;Year;Amount
1. Bob;2018;20
2. Bob;2022;14
3. Joe;2019;40
4. Ben;2017;12
The PK is Name and Year. I have a trigger in place that prevents a user from editing a row's Year to a lesser number. i.e. changing row 3 from 2019 to 2018.
That trigger is below:
Replace TRIGGER xyz.Month_Update
AFTER UPDATE OF Month ON xyz.table
REFERENCING OLD ROW as OldRow NEW ROW as NewRow
FOR EACH ROW
WHEN NewRow.Year < OldRow.Year
abort;
Now I would like to do something similar for insert.
I would like to prevent a user from inserting a new row into the table
if..
There already exists a row for the same person in the table and
That row(s) has a greater year than the one the user is attempting to enter
i.e. User can't enter Joe;2017;19 but user can enter Joe;2020;19
There are a few obvious problems with the trigger below but it shows the general idea:
Replace TRIGGER xyz.Month_Update
AFTER INSERT ON xyz.table
REFERENCING NEW ROW as NewRow
FOR EACH ROW
WHEN NewRow.Year < (select max(year) from xyz.table as t1 where t1.name = NewRow.name group by t1.name)
abort;
I'm new to triggers in general and teradata documentation appears porous.. any suggestions are greatly appreciated.
I don't know exactly if a Correlated Subquery like this is allowed in a Trigger (and how to write it correctly), but you can use Macros or Stored Procedures in a Trigger:
REPLACE MACRO xyz.Month_Insert_macro(yr INT, name VARCHAR(50)) AS
( ABORT 'Greater year already exists for that name'
WHERE :yr <
( SELECT Max(yr) FROM xyz.table
WHERE name= :name
);
);
Replace TRIGGER xyz.Month_Insert
-- it's better to abort BEFORE the Insert than AFTER (same for your Update Trigger)
BEFORE INSERT ON xyz.table
REFERENCING NEW ROW as NewRow
FOR EACH ROW
EXEC xyz.Month_Insert_macro(NewRow.yr, NewRow.name);

SQL Server : make update trigger don't activate with no changing value

I want to track the update changes in a table via a trigger:
CREATE TABLE dbo.TrackTable(...columns same as target table)
GO
CREATE TRIGGER dboTrackTable
ON dbo.TargetTable
AFTER UPDATE
AS
INSERT INTO dbo.TrackTable (...columns)
SELECT (...columns)
FROM Inserted
However in real production some of the update queries select rows with vague conditions and update them all regardless of whether they are actually changed, like
UPDATE Targettable
SET customer_type = 'VIP'
WHERE 1 = 1
--or is_obsolete = 0 or register_date < '20160101' something
But due to table size and to analyze, I only want to choose those actually modified data for tracking. How to achieve this goal?
My track table has many columns (so I do not prefer checking inserted and deleted column one by one) but it seldom changes structure.
I guess the following code will be useful.
CREATE TABLE dbo.TrackTable(...columns same as target table)
GO
CREATE TRIGGER dboTrackTable
ON dbo.TargetTable
AFTER UPDATE
AS
INSERT INTO dbo.TrackTable (...columns)
SELECT *
FROM Inserted
EXCEPT
SELECT *
FROM Deleted
I realize this post is a couple months old now, but for anyone looking for a well-rounded answer:
To exit the trigger if no rows were affected on SQL Server 2016 and up, Microsoft recommends using the built-in ROWCOUNT_BIG() function in the Optimizing DML Triggers section of the Create Trigger documentation.
Usage:
IF ROWCOUNT_BIG() = 0
RETURN;
To ensure you are excluding rows that were not changed, you'll need to do a compare of the inserted and deleted tables inside the trigger. Taking your example code:
INSERT INTO dbo.TrackTable (...columns)
SELECT (...columns)
FROM Inserted i
INNER JOIN deleted d
ON d.[SomePrimaryKeyCol]=i.[SomePrimaryKeyCol] AND
i.customer_type<>d.customer_type
Microsoft documentation and w3schools are great resources for learning how to leverage various types of queries and trigger best practices.
Prevent trigger from doing anything if no rows changed.
Writing-triggers-the-right-way
CREATE TRIGGER the_trigger on dbo.Data
after update
as
begin
if ##ROWCOUNT = 0
return
set nocount on
/* Some Code Here */
end
Get a list of rows that changed:
CREATE TRIGGER the_trigger on dbo.data
AFTER UPDATE
AS
SELECT * from inserted
Previous stack overflow on triggers
#anna - as per #Oded's answer, when an update is performed, the rows are in the deleted table with the old information, and the inserted table with the new information –

SQL Server trigger : update query new vs old data

I have two tables. One has employee info in it. The other I want to add records to based on this employee table.
What I want to happen is whenever there are salary adjustments made to the employee table (by an UDPATE query), the extra table will have a row added to it containing a new event ID and the amount by which salaries have been adjusted (so if 5 people's salaries are increased by £1000, then the row will have the adjustment at £5000).
I've created the trigger and it adds the row with each update. However what it doesn't do is only bring in the additional salary. I can't think how to do that. I have this code so far;
Create trigger Salaryupdate
On TBL_Employees
For update
As
Insert TBL_audit (notes,Delta,AdjDate)
Select 'Salary update', sum(salary), getdate()
From TBL_Employees
I know the sum bit is wrong as I only want the change in salary value, not the total sum.
How can I find the difference between new and old values for the changed rows (or other method)?
I'm using SQL Server 2008.
You should be using the deleted and inserted tables in a trigger. So, I think:
Create trigger Salaryupdate
On TBL_Employees
For update
As
Insert TBL_audit(notes, Delta, AdjDate)
Select 'Salary update',
coalesce(newsalary, 0) - coalesce(oldsalary, 0),
getdate()
From (select sum(salary) as newsalary from inserted) i cross join
(select sum(salary) as oldsalary from deleted) d;
Also, in SQL Server you can set AdjDate to have a default value of getdate() -- that way, the database takes care of setting the value when you insert another row.

AFTER UPDATE trigger using an aggregate from the updated table

I'm having trouble with an update trigger. I want the trigger to set Quarterbacks.Yards equal to the sum of Wide receiving yards if they're on the same team.
create trigger T_Receiving_Passing
on NFL.Widereceivers
after update
as
begin
declare
#receivingyards int,
select #receivingyards = sum (Widereceivers.Yards) from NFL.Widereceivers
update NFL.Quarterbacks
set Quarterbacks.Yards = #receivingyards
where Quarterbacks.Team = Widereceivers.Team
end
At the end of the statement, Widereceivers.Team is underlined in red, and it is causing errors. I get this same error whenever I try to reference a column in another table without naming the table in a from clause. How can I fix this problem?
Ok, you should be able to do this without the SELECT statement or its variable and instead use a more complex UPDATE statement joining on the special inserted table which holds the new values from the update.
CREATE TRIGGER T_Receiving_Passing
ON NFL.Widereceivers
AFTER UPDATE
AS
UPDATE NFL.Quarterbacks
-- Get the SUM() in a subselect
SET Quarterbacks.Yards = (SELECT SUM(Yards) FROM Widereceivers WHERE Team = inserted.Team)
FROM
NFL.Quarterbacks
-- Join against the special inserted table
JOIN inserted ON Quarterbacks.Team = inserted.Team
GO
Here is a proof of concept
In your original attempt, you hoped to use a SELECT query first to populate a scalar variable. In an UPDATE statement however, you can use a subselect that returns exactly one column of exactly one row inside the SET clause to retrieve a new value.
Since your requirement was to use an aggregate SUM() it isn't as straightforward as assigning a value directly from the inserted like SET Yards = inserted.Yards. Instead, the subselect produces the aggregate sum limited to just the Team used in the inserted row.
As far as the inserted/deleted tables go, review the official documentation. I have not worked with SQL Server regularly for a few years but if I recall correctly, the inserted table must occur in the FROM clause which implies it will usually need to be JOINed in. In your UPDATE statement, inserted is needed in both the subselect and the outer query, so it was joined in the outer one.

update multiple tables with trigger

I want to update two tables when a user wants to update a view.
create trigger update_mID
instead of update of mID on LateRating
for each row
begin
update Movie, Rating
set mID = new.mID
where mID = Old.mID;
end;
I want to update bot the Movie relation and the Rating relation, however, I have not yet experienced a trigger that is able to update multiple tables. Can someone please indicate how I can overcome this?
UPDATE: This is for a exercise to test my trigger scripting skills. The requirement is that I have to write it in one trigger query. #CL. I tried putting two update statements between the begin and end keywords, however, it says that there is a syntax error.... is there a specific way to put two updates between the begin and end?
A single UPDATE statement can modify only a single table.
Use two UPDATEs:
UPDATE Movie SET mID = NEW.mID WHERE mID = OLD.mID;
UPDATE Rating SET mID = NEW.mID WHERE mID = OLD.mID;
You could do a REPLACE INTO statement like the following:
DROP TRIGGER IF EXISTS `update_mID`;CREATE DEFINER=`USER`#`localhost` TRIGGER
`update_mID` AFTER UPDATE ON `tblname` FOR EACH ROW REPLACE INTO
USER_DATABASENAME.TBLNAME (COLUMNNAME1,COLUMNNAME1) SELECT COLUMNNAME1,COLUMNNAME1
FROM USER_DBNAME.TBLNAME
This can even be two separate databases like the example below:
DROP TRIGGER IF EXISTS `update_mID`;CREATE DEFINER=`USER`#`localhost` TRIGGER
`update_mID` AFTER UPDATE ON `tblname from DB1` FOR EACH ROW REPLACE INTO
USER_DATABASENAME1.TBLNAMEDB2 (COLUMNNAME1,COLUMNNAME1) SELECT
COLUMNNAME1,COLUMNNAME1 FROM USER_DBNAME2.TBLNAME