<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://sqlservernation.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Tip of the Week : SQL Server, INSERTED</title><link>http://sqlservernation.com/blogs/tipweek/archive/tags/SQL+Server/INSERTED/default.aspx</link><description>Tags: SQL Server, INSERTED</description><dc:language>en</dc:language><generator>CommunityServer 2008.5 SP2 (Build: 40407.4157)</generator><item><title>SQL Server Triggers</title><link>http://sqlservernation.com/blogs/tipweek/archive/2010/02/28/handling-trigger-logic.aspx</link><pubDate>Mon, 01 Mar 2010 01:44:00 GMT</pubDate><guid isPermaLink="false">a3dbd76b-19b9-441f-b40a-d79e8f433d35:361</guid><dc:creator>TimChapman</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://sqlservernation.com/blogs/tipweek/rsscomments.aspx?PostID=361</wfw:commentRss><comments>http://sqlservernation.com/blogs/tipweek/archive/2010/02/28/handling-trigger-logic.aspx#comments</comments><description>&lt;p&gt;Handling trigger is a very simple, and very misunderstood facet of SQL Server programming.&amp;nbsp; Today I&amp;#39;ll take a look some very common trigger scenarios, some mistakes made, and how you can program around them.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;First, a little bit of background of the types of triggers available in SQL Server.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Trigger Types&lt;/b&gt;&lt;br /&gt;There are two different types of SQL Server triggers to date: INSTEAD OF triggers and AFTER triggers.&amp;nbsp;&lt;span style="text-decoration:underline;"&gt;&lt;i&gt; &lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;INSTEAD OF&lt;/b&gt; triggers are placed on tables and views (most often views) and are invoked INSTEAD OF the action taken.&amp;nbsp; For example, if I have defined an INSTEAD OF trigger on a view to execute in the event of an INSERT statement, the trigger would be invoked INSTEAD OF my INSERT statement and actions could be taken to handle the intended statement.&amp;nbsp; This is ideal for views because it allows the developer to abstract triggers on the view code so that the view behaves like a table and without the overhead of the user knowing how to appropriately handle with the underlying tables.&amp;nbsp; For example, I could define a view named vw_AllSalesView that abstracts sales data among 10 different tables.&amp;nbsp; I could then define an INSTEAD OF trigger on this view which will insert data into the appropriate table(s) when an INSERT statement is ran against the view.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;AFTER &lt;/b&gt;triggers are actions that are taken AFTER an action is taken on a table.&amp;nbsp; This is really the most common type of trigger, and is ran against tables (not views).&amp;nbsp; For example, you have a situation where you need want to capture any new records inserted into a table into a processing table for later work.&amp;nbsp; You could define an AFTER trigger on the primary table to capture the INSERTED records AFTER the action is taken.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;It is very important to note the similarities and differences between an INSTEAD OF and an AFTER trigger.&amp;nbsp; &lt;b&gt;Both trigger types execute in the context of the original transaction.&lt;/b&gt;&amp;nbsp; The INSTEAD OF trigger overtakes the transaction and completes its work rather than the original DML statement.&amp;nbsp; The scope of a transaction of an AFTER trigger ends when the original statement has completed AND after the work by the AFTER trigger.&amp;nbsp; The major differences between the two trigger types is the scope of work that they accomplish.&amp;nbsp; Remember that the INSTEAD OF replaces in full the work of the original intended DML statement.&amp;nbsp; The AFTER trigger still allows the original statement to occur, and runs after it has completed.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Pseudo Tables&lt;/b&gt;&lt;br /&gt;So, how does a trigger have access to the records that were inserted/modified/or deleted (or intended to be inserted/modified/or deleted in the case of an INSTEAD OF trigger)?&amp;nbsp; Well, the records from the modifications are stored in two different internal SQL Server tables.&lt;/p&gt;
&lt;p&gt;&lt;span style="text-decoration:underline;"&gt;INSERTED&lt;/span&gt;&lt;br /&gt;The INSERTED pseudo-table stores records that were newly inserted into a table in the case of an INSERT statement.&amp;nbsp; This table also contains the &lt;b&gt;new &lt;/b&gt;values for the records that were modified through the use of an UPDATE statement.&lt;/p&gt;
&lt;p&gt;&lt;span style="text-decoration:underline;"&gt;DELETED&lt;/span&gt;&lt;br /&gt;The DELETED pseudo-table stores records that were deleted from the table through a DELETE statement.&amp;nbsp; This table also contains the &lt;b&gt;old&lt;/b&gt; values for the records that were modified through the use of an UPDATE statement.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Granularity&lt;/b&gt;&lt;br /&gt;All SQL Server triggers are handled at a set-based granularity.&amp;nbsp; This means that whatever records are updated by a&amp;nbsp;certain statement will also be available in the INSERTED and DELETED tables.&amp;nbsp; This&amp;nbsp;is one of the primary problems I have seen when triggers are written.&amp;nbsp; SQL Server does not have a &amp;quot;FOR EACH ROW&amp;quot; feature when handling trigger records.&amp;nbsp; In the event (usually a RARE event) that you need to take special actions on each record in one of the pseudo tables, such as calling a procedure for each value, a loop construct is how you&amp;#39;ll need to accomplish it.&amp;nbsp; (More on this later.)&lt;/p&gt;
&lt;p&gt;So, lets take a look at a few common trigger-based programming mistakes and what you can do to get around them.&amp;nbsp; (Think SET-BASED) First, lets create a table and load some data into it.&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-size:12px;width:100%;color:#000000;line-height:14px;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;background-color:#eeeeee;"&gt;&lt;code&gt;IF OBJECT_ID(&amp;#39;SalesHistory&amp;#39;) IS NOT NULL&lt;br /&gt;DROP TABLE SalesHistory;&lt;br /&gt;GO&lt;br /&gt;CREATE TABLE [dbo].[SalesHistory]&lt;br /&gt;(     &lt;br /&gt;    [SaleID] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,     &lt;br /&gt;    [Product] [varchar](50) NULL,     &lt;br /&gt;    [SaleDate] [datetime] NULL,     &lt;br /&gt;    [SalePrice] [money] NULL&lt;br /&gt;)&lt;br /&gt;GO    &lt;br /&gt;&lt;br /&gt;DECLARE @i SMALLINT&lt;br /&gt;SET @i = 1&lt;br /&gt;&lt;br /&gt;WHILE (@i &amp;lt;=100)&lt;br /&gt;BEGIN                 &lt;br /&gt;    INSERT INTO SalesHistory(Product, SaleDate, SalePrice)                     &lt;br /&gt;    VALUES (&amp;#39;Computer&amp;#39;, DATEADD(mm, @i, &amp;#39;3/11/1919&amp;#39;), DATEPART(ms, GETDATE()) + (@i + 57))          &lt;br /&gt;&lt;br /&gt;    INSERT INTO SalesHistory(Product, SaleDate, SalePrice)     &lt;br /&gt;    VALUES (&amp;#39;BigScreen&amp;#39;, DATEADD(mm, @i, &amp;#39;3/11/1927&amp;#39;), DATEPART(ms, GETDATE()) + (@i + 13))                      &lt;br /&gt;&lt;br /&gt;    INSERT INTO SalesHistory(Product, SaleDate, SalePrice)            &lt;br /&gt;    VALUES (&amp;#39;PoolTable&amp;#39;, DATEADD(mm, @i, &amp;#39;3/11/1908&amp;#39;), DATEPART(ms, GETDATE()) + (@i + 29))                      &lt;br /&gt;&lt;br /&gt;    SET @i = @i + 1    &lt;br /&gt;&lt;br /&gt;END&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now to the trigger.&amp;nbsp; Below is the format that I sometimes see on forum posts, usually along with the complaint of &amp;quot;Why isn&amp;#39;t my trigger working?&amp;quot;&amp;nbsp; Lets take a look at why it doesn&amp;#39;t really do what is intended.&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-size:12px;width:100%;color:#000000;line-height:14px;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;background-color:#eeeeee;"&gt;&lt;code&gt;CREATE TRIGGER tr_SalesHistory_Update&lt;br /&gt;ON SalesHistory&lt;br /&gt;AFTER UPDATE&lt;br /&gt;AS&lt;br /&gt;BEGIN&lt;br /&gt;    DECLARE @Product VARCHAR(50), @SaleID INT&lt;br /&gt;    SET @Product = (SELECT Product FROM INSERTED)&lt;br /&gt;    SET @SaleID = (SELECT SaleID FROM INSERTED)&lt;br /&gt;    SELECT @Product, @SaleID&lt;br /&gt;END&lt;br /&gt; &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I can run an UPDATE statement to see how well my fresh new trigger works.&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-size:12px;width:100%;color:#000000;line-height:14px;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;background-color:#eeeeee;"&gt;&lt;code&gt;UPDATE TOP(5) SalesHistory&lt;br /&gt;SET SaleDate = SaleDate&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My UPDATE statement causes an error.&amp;nbsp; It seems that I have a sub-query that returns more than one value that I&amp;#39;m trying to assign to a variable.&amp;nbsp; I am actually VERY lucky to receive this error.&amp;nbsp; This tells me that something is very wrong with my code, and that I probably need to take a step back and reconsider how I&amp;#39;m handling it.&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-size:12px;width:100%;color:#000000;line-height:14px;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;background-color:#eeeeee;"&gt;&lt;code&gt;Msg 512, Level 16, State 1, Procedure tr_SalesHistory_Update, Line 7&lt;br /&gt;Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, &amp;lt;, &amp;lt;= , &amp;gt;, &amp;gt;= or when the subquery is used as an expression.&lt;br /&gt;The statement has been terminated.&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, instead of rethinking how I&amp;#39;m writing my trigger, I&amp;#39;ll do a little rearranging to resolve my error.&amp;nbsp; This time I&amp;#39;ll include the functionality of using the values from my INSERTED table to update an archive table.&amp;nbsp; I&amp;#39;ll also show some rows at the end of the trigger so I can verify what I am doing is what is intended.&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-size:12px;width:100%;color:#000000;line-height:14px;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;background-color:#eeeeee;"&gt;&lt;code&gt;ALTER TRIGGER tr_SalesHistory_Update&lt;br /&gt;ON SalesHistory&lt;br /&gt;AFTER UPDATE&lt;br /&gt;AS&lt;br /&gt;BEGIN&lt;br /&gt;    DECLARE @Product VARCHAR(50), @SaleID INT&lt;br /&gt;&lt;br /&gt;    SELECT @Product = Product, @SaleID = SaleID&lt;br /&gt;    FROM INSERTED&lt;br /&gt;&lt;br /&gt;    UPDATE sf&lt;br /&gt;    SET Product = @Product&lt;br /&gt;    FROM SalesHistoryArchive sf&lt;br /&gt;    WHERE SaleID = @SaleID&lt;br /&gt;&lt;br /&gt;    SELECT @Product, Product, @SaleID, SaleID&lt;br /&gt;    FROM INSERTED&lt;br /&gt;&lt;br /&gt;END&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, lets run the same UPDATE statement again:&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-size:12px;width:100%;color:#000000;line-height:14px;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;background-color:#eeeeee;"&gt;&lt;code&gt;UPDATE TOP(5) SalesHistory&lt;br /&gt;SET SaleDate = SaleDate&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Below is the output from my trigger. The first and the third columns represent the values from my variables that I captured from the INSERTED table and the second and fourth columns represent the actual values from the table.&amp;nbsp; As you can see, my variables only contain the values from the first (just &lt;i&gt;happened &lt;/i&gt;to be the first record in this case) record from my batch.&amp;nbsp; I updated the SalesHistoryArchive table with these values, rather than from all of the values from the INSERTED table.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.Handling+Trigger+Logic/2402.TriggerLogicOutput.JPG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.Handling+Trigger+Logic/2402.TriggerLogicOutput.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Luckily, the change for this is easy to make.&amp;nbsp; I can use a set-based approach and get around the use of variable entirely.&amp;nbsp; I can join my INSERTED table to the SalesHistoryArchive table to achieve my desired result.&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-size:12px;width:100%;color:#000000;line-height:14px;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;background-color:#eeeeee;"&gt;&lt;code&gt;ALTER TRIGGER tr_SalesHistory_Update&lt;br /&gt;ON SalesHistory&lt;br /&gt;AFTER UPDATE&lt;br /&gt;AS&lt;br /&gt;BEGIN&lt;br /&gt;&lt;br /&gt;    UPDATE sf&lt;br /&gt;    SET Product = i.Product&lt;br /&gt;    FROM SalesHistoryArchive sf&lt;br /&gt;    JOIN INSERTED i ON sf.SaleID = i.SaleID&lt;br /&gt;&lt;br /&gt;END&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Handling Each Value&lt;/b&gt;&lt;br /&gt;So, what can you do if you need to call a stored procedure for each value altered and captured in your trigger?&amp;nbsp; There are two main ways to handle this situation; store the records in a table and handle them later, or iterate through them in the trigger and call the procedure.&amp;nbsp; The first way is relatively straightforward (and by far the best option of the two).&amp;nbsp; In this scenario, a queue-table of sorts is used to capture output from the INSERTED or DELETED tables in the trigger.&amp;nbsp; Later, a process acts on the records in this table and cleans them up once its work is done.&amp;nbsp; Iteration is still likely used in this scenario, but not at the expense of the original transaction as would be the case if a WHILE LOOP or CURSOR were used in a trigger.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;With the cursor approach, the INSERTED or DELETED records are loaded into a cursor (or WHILE LOOP) and the data set is iterated.&amp;nbsp; At each iteration, the values from one row in the pseduo table is passed to a stored procedure.&amp;nbsp; The main drawback to this apporach is that the entire data set must be iterated through BEFORE the intial transaction has completed.&amp;nbsp; If it was a large update, the scope of the transaction becomes much larger, causing the duration of the locks on the table to increase.&amp;nbsp; This causes nasty blocking and potentially deadlocks.&amp;nbsp; When faced with such a situation, I strongly encourage you to store the captured values in a table for later processing.&amp;nbsp; It requires a little extra work up front, but pays off in the end due to less stress on the database.&lt;/p&gt;
&lt;p&gt;Tim&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://sqlservernation.com/aggbug.aspx?PostID=361" width="1" height="1"&gt;</description><category domain="http://sqlservernation.com/blogs/tipweek/archive/tags/SQL+Server/default.aspx">SQL Server</category><category domain="http://sqlservernation.com/blogs/tipweek/archive/tags/trigger/default.aspx">trigger</category><category domain="http://sqlservernation.com/blogs/tipweek/archive/tags/loop/default.aspx">loop</category><category domain="http://sqlservernation.com/blogs/tipweek/archive/tags/INSERTED/default.aspx">INSERTED</category><category domain="http://sqlservernation.com/blogs/tipweek/archive/tags/CURSOR/default.aspx">CURSOR</category><category domain="http://sqlservernation.com/blogs/tipweek/archive/tags/logic/default.aspx">logic</category><category domain="http://sqlservernation.com/blogs/tipweek/archive/tags/DML/default.aspx">DML</category><category domain="http://sqlservernation.com/blogs/tipweek/archive/tags/DELETED/default.aspx">DELETED</category></item></channel></rss>