<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:copyright="http://blogs.law.harvard.edu/tech/rss" xmlns:image="http://purl.org/rss/1.0/modules/image/">
    <channel>
        <title>SQL Server</title>
        <link>http://blogs.interakting.co.uk/steve/category/49.aspx</link>
        <description>Microsoft SQL Server (any version)</description>
        <language>en-GB</language>
        <copyright>Stephen Horsfield</copyright>
        <managingEditor>stephen.horsfield@interakting.co.uk</managingEditor>
        <generator>Subtext Version 1.9.5.177</generator>
        <item>
            <title>SQL Server: Dynamic column lists with UNPIVOT &amp;mdash; Give your support</title>
            <link>http://blogs.interakting.co.uk/steve/archive/2008/03/11/SQL-Server-Dynamic-column-lists-with-UNPIVOT-mdash-Give-your.aspx</link>
            <description>&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here's another product suggestion I've logged with Microsoft along the same lines as the previous one.  This one is to allow you to dynamically specify the list of columns you want to unpivot.  This might be from a stored procedure parameter or a subquery, either way you can't do it easily at the moment.  Register your support here:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=332512"&gt;https://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=332512&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Detail&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Current behaviour&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;UNPIVOT (value FOR column_name IN (Col1, Col2, Col3, Col4))&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;blockquote&gt;
&lt;p&gt;All columns must be known in advance and specified in full.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;Suggested behaviour with subquery using a table-valued parameter&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;UNPIVOT (value FOR column_name IN (SELECT colname FROM @tblParam))&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;em&gt;&lt;/em&gt;&lt;blockquote&gt;
&lt;p&gt;Columns can be determined at runtime.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Versions&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;SQL Server 2008 &lt;/li&gt;
    &lt;li&gt;SQL Server 2005 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Metadata&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Categories: IT Management, Software Development, SQL Server &lt;/li&gt;
    &lt;li&gt;Additional keywords: UNPIVOT, dynamic column lists &lt;/li&gt;
&lt;li&gt;Technorati Tags: 
&lt;a href="http://technorati.com/tags/SQL" rel="tag"&gt;SQL&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/SQL%20Server" rel="tag"&gt;SQL Server&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/UNPIVOT" rel="tag"&gt;UNPIVOT&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;img src="http://blogs.interakting.co.uk/steve/aggbug/225.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Stephen Horsfield</dc:creator>
            <guid>http://blogs.interakting.co.uk/steve/archive/2008/03/11/SQL-Server-Dynamic-column-lists-with-UNPIVOT-mdash-Give-your.aspx</guid>
            <pubDate>Tue, 11 Mar 2008 12:36:56 GMT</pubDate>
            <wfw:comment>http://blogs.interakting.co.uk/steve/comments/225.aspx</wfw:comment>
            <comments>http://blogs.interakting.co.uk/steve/archive/2008/03/11/SQL-Server-Dynamic-column-lists-with-UNPIVOT-mdash-Give-your.aspx#feedback</comments>
            <wfw:commentRss>http://blogs.interakting.co.uk/steve/comments/commentRss/225.aspx</wfw:commentRss>
        </item>
        <item>
            <title>SQL Server: Inclusion of NULLs with UNPIVOT &amp;mdash; Give your support</title>
            <link>http://blogs.interakting.co.uk/steve/archive/2008/03/11/SQL-Server-Inclusion-of-NULLs-with-UNPIVOT-mdash-Give-your.aspx</link>
            <description>&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I've logged a suggestion with Microsoft for support for the inclusion of NULLs when you use the UNPIVOT operator.  Add your support if you think this would be useful:&lt;/p&gt;
&lt;p&gt;&lt;a title="https://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=332325" href="https://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=332325"&gt;https://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=332325&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Source data&lt;/em&gt;&lt;/p&gt;
&lt;table cellspacing="5" cellpadding="2" width="400" border="1"&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="80"&gt;&lt;strong&gt;&lt;u&gt;ID&lt;/u&gt;&lt;/strong&gt;&lt;/td&gt;
            &lt;td valign="top" width="80"&gt;&lt;strong&gt;&lt;u&gt;Col1&lt;/u&gt;&lt;/strong&gt;&lt;/td&gt;
            &lt;td valign="top" width="80"&gt;&lt;strong&gt;&lt;u&gt;Col2&lt;/u&gt;&lt;/strong&gt;&lt;/td&gt;
            &lt;td valign="top" width="80"&gt;&lt;strong&gt;&lt;u&gt;Col3&lt;/u&gt;&lt;/strong&gt;&lt;/td&gt;
            &lt;td valign="top" width="80"&gt;&lt;strong&gt;&lt;u&gt;Col4&lt;/u&gt;&lt;/strong&gt;&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="80"&gt;1&lt;/td&gt;
            &lt;td valign="top" width="80"&gt;Value&lt;/td&gt;
            &lt;td valign="top" width="80"&gt;&lt;strong&gt;NULL&lt;/strong&gt;&lt;/td&gt;
            &lt;td valign="top" width="80"&gt;Value&lt;/td&gt;
            &lt;td valign="top" width="80"&gt;&lt;strong&gt;NULL&lt;/strong&gt;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;Current behaviour&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UNPIVOT (value FOR column_name IN (Col1, Col2, Col3, Col4))&lt;/strong&gt;&lt;/p&gt;
&lt;table cellspacing="5" cellpadding="2" width="400" border="1"&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="133"&gt;&lt;strong&gt;&lt;u&gt;ID&lt;/u&gt;&lt;/strong&gt;&lt;/td&gt;
            &lt;td valign="top" width="133"&gt;&lt;strong&gt;&lt;u&gt;column_name&lt;/u&gt;&lt;/strong&gt;&lt;/td&gt;
            &lt;td valign="top" width="133"&gt;&lt;strong&gt;&lt;u&gt;value&lt;/u&gt;&lt;/strong&gt;&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="133"&gt;1&lt;/td&gt;
            &lt;td valign="top" width="133"&gt;Col1&lt;/td&gt;
            &lt;td valign="top" width="133"&gt;Value&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="133"&gt;1&lt;/td&gt;
            &lt;td valign="top" width="133"&gt;Col3&lt;/td&gt;
            &lt;td valign="top" width="133"&gt;Value&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;Suggested behaviour with new INCLUDE NULL optional clause&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UNPIVOT (value FOR column_name IN (Col1, Col2, Col3, Col4) INCLUDE NULL)&lt;/strong&gt;&lt;/p&gt;
&lt;table cellspacing="5" cellpadding="2" width="400" border="1"&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="133"&gt;&lt;strong&gt;&lt;u&gt;ID&lt;/u&gt;&lt;/strong&gt;&lt;/td&gt;
            &lt;td valign="top" width="133"&gt;&lt;strong&gt;&lt;u&gt;column_name&lt;/u&gt;&lt;/strong&gt;&lt;/td&gt;
            &lt;td valign="top" width="133"&gt;&lt;strong&gt;&lt;u&gt;value&lt;/u&gt;&lt;/strong&gt;&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="133"&gt;1&lt;/td&gt;
            &lt;td valign="top" width="133"&gt;Col1&lt;/td&gt;
            &lt;td valign="top" width="133"&gt;Value&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="133"&gt;1&lt;/td&gt;
            &lt;td valign="top" width="133"&gt;Col2&lt;/td&gt;
            &lt;td valign="top" width="133"&gt;&lt;strong&gt;NULL&lt;/strong&gt;&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="133"&gt;1&lt;/td&gt;
            &lt;td valign="top" width="133"&gt;Col3&lt;/td&gt;
            &lt;td valign="top" width="133"&gt;Value&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td valign="top" width="133"&gt;1&lt;/td&gt;
            &lt;td valign="top" width="133"&gt;Col4&lt;/td&gt;
            &lt;td valign="top" width="133"&gt;&lt;strong&gt;NULL&lt;/strong&gt;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;
&lt;em&gt;&lt;/em&gt;
&lt;p&gt;This feature is available in Oracle 11g, for example.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Versions&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;SQL Server 2008 &lt;/li&gt;
    &lt;li&gt;SQL Server 2005 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Metadata&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Categories: IT Management, Software Development, SQL Server &lt;/li&gt;
    &lt;li&gt;Additional keywords: UNPIVOT, NULL &lt;/li&gt;
    &lt;li&gt;Technorati Tags: &lt;a rel="tag" href="http://technorati.com/tags/SQL"&gt;SQL&lt;/a&gt;, &lt;a rel="tag" href="http://technorati.com/tags/SQL%20Server"&gt;SQL Server&lt;/a&gt;, &lt;a rel="tag" href="http://technorati.com/tags/UNPIVOT"&gt;UNPIVOT&lt;/a&gt;, &lt;a rel="tag" href="http://technorati.com/tags/NULL"&gt;NULL&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;&lt;img src="http://blogs.interakting.co.uk/steve/aggbug/224.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Stephen Horsfield</dc:creator>
            <guid>http://blogs.interakting.co.uk/steve/archive/2008/03/11/SQL-Server-Inclusion-of-NULLs-with-UNPIVOT-mdash-Give-your.aspx</guid>
            <pubDate>Tue, 11 Mar 2008 10:54:54 GMT</pubDate>
            <wfw:comment>http://blogs.interakting.co.uk/steve/comments/224.aspx</wfw:comment>
            <comments>http://blogs.interakting.co.uk/steve/archive/2008/03/11/SQL-Server-Inclusion-of-NULLs-with-UNPIVOT-mdash-Give-your.aspx#feedback</comments>
            <wfw:commentRss>http://blogs.interakting.co.uk/steve/comments/commentRss/224.aspx</wfw:commentRss>
        </item>
        <item>
            <title>SQL Server: Fun with column-level auditing</title>
            <link>http://blogs.interakting.co.uk/steve/archive/2008/03/10/SQL-Server-Fun-with-column-level-auditing.aspx</link>
            <description>&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Auditing the actual changes made to a table can be quite complex, but is often required.  In &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; 2008, you can use the new feature &lt;a title="http://msdn2.microsoft.com/en-us/library/bb522489(SQL.100).aspx (Microsoft MSDN)" target="_blank" href="http://msdn2.microsoft.com/en-us/library/bb522489(SQL.100).aspx"&gt;Change Data Capture&lt;/a&gt;, but in 2005 you have fewer options.&lt;/p&gt;
&lt;p&gt;Here's an approach using common table expressions, table variables and a nice, big and hopefully fast query!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Context&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;&lt;a title="http://blogs.bdnet.co.uk/steve/archive/2008/03/10/SQL-Server-Fun-with-column-level-auditing.aspx" target="_self" href="http://blogs.bdnet.co.uk/steve/archive/2008/03/10/SQL-Server-Fun-with-column-level-auditing.aspx"&gt;click here to view this online and see the code formatted for viewing&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Suppose you have a data table and an audit table as follows:&lt;/p&gt;
&lt;div&gt;
&lt;div class="csharpcode"&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;   1:&lt;/span&gt; &lt;span class="kwrd"&gt;CREATE&lt;/span&gt; &lt;span class="kwrd"&gt;TABLE&lt;/span&gt; tblToAudit (&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;   2:&lt;/span&gt;     pkid &lt;span class="kwrd"&gt;int&lt;/span&gt; &lt;span class="kwrd"&gt;identity&lt;/span&gt;(1,1) &lt;span class="kwrd"&gt;primary&lt;/span&gt; &lt;span class="kwrd"&gt;key&lt;/span&gt;,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;   3:&lt;/span&gt;     col0 nvarchar(20),&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;   4:&lt;/span&gt;     col1 nvarchar(20),&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;   5:&lt;/span&gt;     col2 nvarchar(20),&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;   6:&lt;/span&gt;     col3 nvarchar(20),&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;   7:&lt;/span&gt;     col4 nvarchar(20),&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;   8:&lt;/span&gt;     col5 nvarchar(20),&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;   9:&lt;/span&gt;     col6 nvarchar(20),&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  10:&lt;/span&gt;     col7 nvarchar(20),&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  11:&lt;/span&gt;     col8 nvarchar(20),&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  12:&lt;/span&gt;     col9 nvarchar(20),&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  13:&lt;/span&gt;     lastModified datetime,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  14:&lt;/span&gt;     lastModifiedBy nvarchar(20)&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  15:&lt;/span&gt;     )&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  16:&lt;/span&gt; &lt;span class="kwrd"&gt;GO&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  17:&lt;/span&gt; &lt;span class="kwrd"&gt;CREATE&lt;/span&gt; &lt;span class="kwrd"&gt;TABLE&lt;/span&gt; tblAudit (&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  18:&lt;/span&gt;   object_name nvarchar(20) &lt;span class="kwrd"&gt;NOT&lt;/span&gt; &lt;span class="kwrd"&gt;NULL&lt;/span&gt;,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  19:&lt;/span&gt;   pkid &lt;span class="kwrd"&gt;int&lt;/span&gt; &lt;span class="kwrd"&gt;NOT&lt;/span&gt; &lt;span class="kwrd"&gt;NULL&lt;/span&gt;,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  20:&lt;/span&gt;   column_name nvarchar(20) &lt;span class="kwrd"&gt;NOT&lt;/span&gt; &lt;span class="kwrd"&gt;NULL&lt;/span&gt;,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  21:&lt;/span&gt;   prior_value nvarchar(20),&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  22:&lt;/span&gt;   new_value nvarchar(20),&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  23:&lt;/span&gt;   modified datetime,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  24:&lt;/span&gt;   modifiedBy nvarchar(20),&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  25:&lt;/span&gt;   msg nvarchar(255)&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  26:&lt;/span&gt; )&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  27:&lt;/span&gt; GO&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And now suppose you have some stored procedures that make changes to one or more rows, possibly also adding new rows (although this isn't particularly well suited to identity columns).  Each column value is passed to the stored procedure using a separate parameter.  How do you audit which columns have changed, and how have they changed?&lt;/p&gt;
&lt;p&gt;One approach is to use successive IF ... THEN clauses, but this is slow and cannot be combined for multiple changes at once.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here's my solution:&lt;/p&gt;
&lt;div&gt;
&lt;div class="csharpcode"&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;   1:&lt;/span&gt; &lt;span class="kwrd"&gt;BEGIN&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;   2:&lt;/span&gt;  &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;   3:&lt;/span&gt; &lt;span class="kwrd"&gt;SET&lt;/span&gt; NOCOUNT &lt;span class="kwrd"&gt;ON&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;   4:&lt;/span&gt;  &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;   5:&lt;/span&gt; &lt;span class="rem"&gt;-- Declare variables to represent the input data&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;   6:&lt;/span&gt; &lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @c0 nvarchar(20) = N&lt;span class="str"&gt;'B'&lt;/span&gt;, @c1 nvarchar(20) = N&lt;span class="str"&gt;'C'&lt;/span&gt;, @c2 nvarchar(20) = N&lt;span class="str"&gt;'B'&lt;/span&gt;,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;   7:&lt;/span&gt;     @c3 nvarchar(20) = &lt;span class="kwrd"&gt;NULL&lt;/span&gt;, @c4 nvarchar(20) = N&lt;span class="str"&gt;'D'&lt;/span&gt;, @c5 nvarchar(20) = N&lt;span class="str"&gt;'A'&lt;/span&gt;,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;   8:&lt;/span&gt;     @c6 nvarchar(20) = &lt;span class="kwrd"&gt;NULL&lt;/span&gt;, @c7 nvarchar(20) = &lt;span class="kwrd"&gt;NULL&lt;/span&gt;, @c8 nvarchar(20) = &lt;span class="kwrd"&gt;NULL&lt;/span&gt;,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;   9:&lt;/span&gt;     @c9 nvarchar(20) = &lt;span class="kwrd"&gt;NULL&lt;/span&gt;, @&lt;span class="kwrd"&gt;user&lt;/span&gt; nvarchar(20) = N&lt;span class="str"&gt;'Test'&lt;/span&gt;,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  10:&lt;/span&gt;     @pkid &lt;span class="kwrd"&gt;int&lt;/span&gt; = 10&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  11:&lt;/span&gt;  &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  12:&lt;/span&gt; &lt;span class="rem"&gt;-- Create a table variable that is equivalent to the source table&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  13:&lt;/span&gt; &lt;span class="rem"&gt;-- table variables are stored in tempdb so make sure we use this&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  14:&lt;/span&gt; &lt;span class="rem"&gt;-- database's collation&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  15:&lt;/span&gt; &lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @tgtData &lt;span class="kwrd"&gt;TABLE&lt;/span&gt; (&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  16:&lt;/span&gt;     pkid &lt;span class="kwrd"&gt;int&lt;/span&gt;,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  17:&lt;/span&gt;     col0 nvarchar(20) &lt;span class="kwrd"&gt;COLLATE&lt;/span&gt; DATABASE_DEFAULT,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  18:&lt;/span&gt;     col1 nvarchar(20) &lt;span class="kwrd"&gt;COLLATE&lt;/span&gt; DATABASE_DEFAULT,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  19:&lt;/span&gt;     col2 nvarchar(20) &lt;span class="kwrd"&gt;COLLATE&lt;/span&gt; DATABASE_DEFAULT,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  20:&lt;/span&gt;     col3 nvarchar(20) &lt;span class="kwrd"&gt;COLLATE&lt;/span&gt; DATABASE_DEFAULT,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  21:&lt;/span&gt;     col4 nvarchar(20) &lt;span class="kwrd"&gt;COLLATE&lt;/span&gt; DATABASE_DEFAULT,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  22:&lt;/span&gt;     col5 nvarchar(20) &lt;span class="kwrd"&gt;COLLATE&lt;/span&gt; DATABASE_DEFAULT,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  23:&lt;/span&gt;     col6 nvarchar(20) &lt;span class="kwrd"&gt;COLLATE&lt;/span&gt; DATABASE_DEFAULT,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  24:&lt;/span&gt;     col7 nvarchar(20) &lt;span class="kwrd"&gt;COLLATE&lt;/span&gt; DATABASE_DEFAULT,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  25:&lt;/span&gt;     col8 nvarchar(20) &lt;span class="kwrd"&gt;COLLATE&lt;/span&gt; DATABASE_DEFAULT,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  26:&lt;/span&gt;     col9 nvarchar(20) &lt;span class="kwrd"&gt;COLLATE&lt;/span&gt; DATABASE_DEFAULT,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  27:&lt;/span&gt;     modified datetime,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  28:&lt;/span&gt;     modifiedBy nvarchar(20) &lt;span class="kwrd"&gt;COLLATE&lt;/span&gt; DATABASE_DEFAULT)&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  29:&lt;/span&gt;  &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  30:&lt;/span&gt; &lt;span class="rem"&gt;-- Insert new values into the table variable, can repeat multiple times&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  31:&lt;/span&gt; INSERT &lt;span class="kwrd"&gt;INTO&lt;/span&gt; @tgtData &lt;span class="kwrd"&gt;VALUES&lt;/span&gt; (@pkid, @c0, @c1, @c2, @c3, @c4, @c5, @c6, &lt;/pre&gt;
&lt;pre class="alteven"&gt;                                   @c7, @c8, @c9, GETDATE(), @&lt;span class="kwrd"&gt;user&lt;/span&gt;)&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  32:&lt;/span&gt;  &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  33:&lt;/span&gt; &lt;span class="rem"&gt;-- This query eliminates NULLs as part of an aggregate so turn ANSI WARNINGS off&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  34:&lt;/span&gt; &lt;span class="kwrd"&gt;SET&lt;/span&gt; ANSI_WARNINGS &lt;span class="kwrd"&gt;OFF&lt;/span&gt;;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  35:&lt;/span&gt;  &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  36:&lt;/span&gt; &lt;span class="rem"&gt;-- Audit and change are one atomic operation, so need a transaction&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  37:&lt;/span&gt; &lt;span class="kwrd"&gt;BEGIN&lt;/span&gt; &lt;span class="kwrd"&gt;TRAN&lt;/span&gt;;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  38:&lt;/span&gt;  &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  39:&lt;/span&gt; &lt;span class="rem"&gt;-- Previous statement is completed with the required ;&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  40:&lt;/span&gt; &lt;span class="rem"&gt;-- THIS IS THE AUDIT statement&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  41:&lt;/span&gt; &lt;span class="kwrd"&gt;WITH&lt;/span&gt; &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  42:&lt;/span&gt; &lt;span class="rem"&gt;-- mergelist is a cartesian join of columns and affected row identifiers&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  43:&lt;/span&gt; mergelist (pkid, colname) &lt;span class="kwrd"&gt;AS&lt;/span&gt; (&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  44:&lt;/span&gt;     &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; pkid, colname&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  45:&lt;/span&gt;     &lt;span class="kwrd"&gt;FROM&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  46:&lt;/span&gt;         &lt;span class="rem"&gt;-- Get a list of row identifiers which are being updated&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  47:&lt;/span&gt;         (&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; &lt;span class="kwrd"&gt;DISTINCT&lt;/span&gt; pkid &lt;span class="kwrd"&gt;FROM&lt;/span&gt; @tgtData) &lt;span class="kwrd"&gt;AS&lt;/span&gt; ids, &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  48:&lt;/span&gt;         &lt;span class="rem"&gt;-- Get a full list of columns&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  49:&lt;/span&gt;         (    &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; &lt;span class="str"&gt;'col0'&lt;/span&gt; &lt;span class="kwrd"&gt;AS&lt;/span&gt; colname &lt;span class="kwrd"&gt;UNION&lt;/span&gt; &lt;span class="kwrd"&gt;ALL&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  50:&lt;/span&gt;             &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; &lt;span class="str"&gt;'col1'&lt;/span&gt; &lt;span class="kwrd"&gt;AS&lt;/span&gt; colname &lt;span class="kwrd"&gt;UNION&lt;/span&gt; &lt;span class="kwrd"&gt;ALL&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  51:&lt;/span&gt;             &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; &lt;span class="str"&gt;'col2'&lt;/span&gt; &lt;span class="kwrd"&gt;AS&lt;/span&gt; colname &lt;span class="kwrd"&gt;UNION&lt;/span&gt; &lt;span class="kwrd"&gt;ALL&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  52:&lt;/span&gt;             &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; &lt;span class="str"&gt;'col3'&lt;/span&gt; &lt;span class="kwrd"&gt;AS&lt;/span&gt; colname &lt;span class="kwrd"&gt;UNION&lt;/span&gt; &lt;span class="kwrd"&gt;ALL&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  53:&lt;/span&gt;             &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; &lt;span class="str"&gt;'col4'&lt;/span&gt; &lt;span class="kwrd"&gt;AS&lt;/span&gt; colname &lt;span class="kwrd"&gt;UNION&lt;/span&gt; &lt;span class="kwrd"&gt;ALL&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  54:&lt;/span&gt;             &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; &lt;span class="str"&gt;'col5'&lt;/span&gt; &lt;span class="kwrd"&gt;AS&lt;/span&gt; colname &lt;span class="kwrd"&gt;UNION&lt;/span&gt; &lt;span class="kwrd"&gt;ALL&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  55:&lt;/span&gt;             &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; &lt;span class="str"&gt;'col6'&lt;/span&gt; &lt;span class="kwrd"&gt;AS&lt;/span&gt; colname &lt;span class="kwrd"&gt;UNION&lt;/span&gt; &lt;span class="kwrd"&gt;ALL&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  56:&lt;/span&gt;             &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; &lt;span class="str"&gt;'col7'&lt;/span&gt; &lt;span class="kwrd"&gt;AS&lt;/span&gt; colname &lt;span class="kwrd"&gt;UNION&lt;/span&gt; &lt;span class="kwrd"&gt;ALL&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  57:&lt;/span&gt;             &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; &lt;span class="str"&gt;'col8'&lt;/span&gt; &lt;span class="kwrd"&gt;AS&lt;/span&gt; colname &lt;span class="kwrd"&gt;UNION&lt;/span&gt; &lt;span class="kwrd"&gt;ALL&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  58:&lt;/span&gt;             &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; &lt;span class="str"&gt;'col9'&lt;/span&gt; &lt;span class="kwrd"&gt;AS&lt;/span&gt; colname&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  59:&lt;/span&gt;         ) &lt;span class="kwrd"&gt;AS&lt;/span&gt; cols&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  60:&lt;/span&gt; ),&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  61:&lt;/span&gt; &lt;span class="rem"&gt;-- srcData is an unpivoted set of column values from the source table with NULLs missing&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  62:&lt;/span&gt; srcData (pkid, colname, &lt;span class="kwrd"&gt;value&lt;/span&gt;) &lt;span class="kwrd"&gt;AS&lt;/span&gt; (&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  63:&lt;/span&gt;     &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; t.pkid, t.colname, t.&lt;span class="kwrd"&gt;value&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  64:&lt;/span&gt;     &lt;span class="kwrd"&gt;FROM&lt;/span&gt; tblToAudit UNPIVOT (&lt;span class="kwrd"&gt;value&lt;/span&gt; &lt;span class="kwrd"&gt;FOR&lt;/span&gt; colname &lt;span class="kwrd"&gt;IN&lt;/span&gt; (col0, col1, col2, col3, col4, col5, &lt;/pre&gt;
&lt;pre class="alteven"&gt;                                                         col6, col7, col8, col9)) &lt;span class="kwrd"&gt;AS&lt;/span&gt; t&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  65:&lt;/span&gt; ),&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  66:&lt;/span&gt; &lt;span class="rem"&gt;-- tgtData is an unpivoted set of column values from the new data with NULLs missing&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  67:&lt;/span&gt; tgtData (pkid, colname, &lt;span class="kwrd"&gt;value&lt;/span&gt;, modified, modifiedBy) &lt;span class="kwrd"&gt;AS&lt;/span&gt; (&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  68:&lt;/span&gt;     &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; t.pkid, t.colname, t.&lt;span class="kwrd"&gt;value&lt;/span&gt;, t.modified, t.modifiedBy&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  69:&lt;/span&gt;     &lt;span class="kwrd"&gt;FROM&lt;/span&gt; @tgtData UNPIVOT (&lt;span class="kwrd"&gt;value&lt;/span&gt; &lt;span class="kwrd"&gt;FOR&lt;/span&gt; colname &lt;span class="kwrd"&gt;IN&lt;/span&gt; (col0, col1, col2, col3, col4, col5, &lt;/pre&gt;
&lt;pre class="alteven"&gt;                                                       col6, col7, col8, col9)) &lt;span class="kwrd"&gt;AS&lt;/span&gt; t&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  70:&lt;/span&gt; ),&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  71:&lt;/span&gt; &lt;span class="rem"&gt;-- changeList is the expanded set of srcData and tgtData including NULLs and with&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  72:&lt;/span&gt; &lt;span class="rem"&gt;-- change indicating which values are NULL&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  73:&lt;/span&gt; changeList (pkid, colname, old_value, new_value, change, modified, modifiedBy) &lt;span class="kwrd"&gt;AS&lt;/span&gt; (&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  74:&lt;/span&gt;     &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; mergelist.pkid, mergelist.colname, srcData.&lt;span class="kwrd"&gt;value&lt;/span&gt;, tgtData.&lt;span class="kwrd"&gt;value&lt;/span&gt;,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  75:&lt;/span&gt;         (2*&lt;span class="kwrd"&gt;COUNT&lt;/span&gt;(srcData.&lt;span class="kwrd"&gt;value&lt;/span&gt;) &lt;span class="kwrd"&gt;OVER&lt;/span&gt; (PARTITION &lt;span class="kwrd"&gt;BY&lt;/span&gt; mergelist.pkid, mergelist.colname)&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  76:&lt;/span&gt;          + &lt;span class="kwrd"&gt;COUNT&lt;/span&gt;(tgtData.&lt;span class="kwrd"&gt;value&lt;/span&gt;) &lt;span class="kwrd"&gt;OVER&lt;/span&gt; (PARTITION &lt;span class="kwrd"&gt;BY&lt;/span&gt; mergelist.pkid, mergelist.colname))&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  77:&lt;/span&gt;         &lt;span class="kwrd"&gt;AS&lt;/span&gt; change,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  78:&lt;/span&gt;         tgtData.modified, tgtData.modifiedBy&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  79:&lt;/span&gt;     &lt;span class="kwrd"&gt;FROM&lt;/span&gt; mergeList &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  80:&lt;/span&gt;         &lt;span class="kwrd"&gt;LEFT&lt;/span&gt; &lt;span class="kwrd"&gt;JOIN&lt;/span&gt; srcData&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  81:&lt;/span&gt;             &lt;span class="kwrd"&gt;ON&lt;/span&gt; mergelist.pkid = srcData.pkid &lt;span class="kwrd"&gt;AND&lt;/span&gt; mergelist.colname = srcData.colname&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  82:&lt;/span&gt;         &lt;span class="kwrd"&gt;LEFT&lt;/span&gt; &lt;span class="kwrd"&gt;JOIN&lt;/span&gt; tgtData&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  83:&lt;/span&gt;             &lt;span class="kwrd"&gt;ON&lt;/span&gt; mergelist.pkid = tgtData.pkid &lt;span class="kwrd"&gt;AND&lt;/span&gt; mergelist.colname = tgtData.colname&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  84:&lt;/span&gt; )&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  85:&lt;/span&gt; &lt;span class="rem"&gt;-- inserting into the audit table&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  86:&lt;/span&gt; INSERT &lt;span class="kwrd"&gt;INTO&lt;/span&gt; tblAudit ([object_name], pkid, column_name, prior_value, new_value, &lt;/pre&gt;
&lt;pre class="alteven"&gt;                            modified, modifiedBy, msg)&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  87:&lt;/span&gt; &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; N&lt;span class="str"&gt;'tblToAudit'&lt;/span&gt;, pkid, colname, &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  88:&lt;/span&gt;     old_value, new_value, &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  89:&lt;/span&gt;     modified, modifiedBy,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  90:&lt;/span&gt;     &lt;span class="rem"&gt;-- use the change column to determine the action being taken&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  91:&lt;/span&gt;     &lt;span class="kwrd"&gt;CASE&lt;/span&gt; change&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  92:&lt;/span&gt;         &lt;span class="kwrd"&gt;WHEN&lt;/span&gt; 1 &lt;span class="kwrd"&gt;THEN&lt;/span&gt; N&lt;span class="str"&gt;'set'&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  93:&lt;/span&gt;         &lt;span class="kwrd"&gt;WHEN&lt;/span&gt; 2 &lt;span class="kwrd"&gt;THEN&lt;/span&gt; N&lt;span class="str"&gt;'reset'&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  94:&lt;/span&gt;         &lt;span class="kwrd"&gt;WHEN&lt;/span&gt; 3 &lt;span class="kwrd"&gt;THEN&lt;/span&gt; N&lt;span class="str"&gt;'modify'&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  95:&lt;/span&gt;     &lt;span class="kwrd"&gt;END&lt;/span&gt; &lt;span class="kwrd"&gt;AS&lt;/span&gt; msg&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  96:&lt;/span&gt; &lt;span class="kwrd"&gt;FROM&lt;/span&gt; changeList &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  97:&lt;/span&gt; &lt;span class="rem"&gt;-- change = 0 means both source and target values are NULL&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  98:&lt;/span&gt; &lt;span class="kwrd"&gt;WHERE&lt;/span&gt; changeList.change &amp;gt; 0 &lt;span class="kwrd"&gt;AND&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt;  99:&lt;/span&gt; &lt;span class="rem"&gt;-- change = 3 means both source and target are NOT NULL, so need to compare them&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 100:&lt;/span&gt;     (changeList.change &amp;lt; 3 &lt;span class="kwrd"&gt;OR&lt;/span&gt; (old_value &amp;lt;&amp;gt; new_value))&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 101:&lt;/span&gt;  &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 102:&lt;/span&gt; &lt;span class="rem"&gt;-- UPDATE existing rows&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 103:&lt;/span&gt; &lt;span class="kwrd"&gt;UPDATE&lt;/span&gt; tblToAudit &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 104:&lt;/span&gt; &lt;span class="kwrd"&gt;SET&lt;/span&gt; &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 105:&lt;/span&gt;     tblToAudit.col0 = t.col0, &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 106:&lt;/span&gt;     tblToAudit.col1 = t.col1, &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 107:&lt;/span&gt;     tblToAudit.col2 = t.col2,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 108:&lt;/span&gt;     tblToAudit.col3 = t.col3,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 109:&lt;/span&gt;     tblToAudit.col4 = t.col4,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 110:&lt;/span&gt;     tblToAudit.col5 = t.col5,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 111:&lt;/span&gt;     tblToAudit.col6 = t.col6,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 112:&lt;/span&gt;     tblToAudit.col7 = t.col7,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 113:&lt;/span&gt;     tblToAudit.col8 = t.col8,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 114:&lt;/span&gt;     tblToAudit.col9 = t.col9,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 115:&lt;/span&gt;     tblToAudit.lastModified = t.modified,&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 116:&lt;/span&gt;     tblToAudit.lastModifiedBy = t.modifiedBy&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 117:&lt;/span&gt; &lt;span class="kwrd"&gt;FROM&lt;/span&gt; tblToAudit &lt;span class="kwrd"&gt;INNER&lt;/span&gt; &lt;span class="kwrd"&gt;JOIN&lt;/span&gt; @tgtData &lt;span class="kwrd"&gt;AS&lt;/span&gt; t &lt;span class="kwrd"&gt;ON&lt;/span&gt; tblToAudit.pkid = t.pkid&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 118:&lt;/span&gt;  &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 119:&lt;/span&gt; &lt;span class="rem"&gt;-- inserting identity column&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 120:&lt;/span&gt; &lt;span class="kwrd"&gt;SET&lt;/span&gt; &lt;span class="kwrd"&gt;IDENTITY_INSERT&lt;/span&gt; tblToAudit &lt;span class="kwrd"&gt;ON&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 121:&lt;/span&gt;  &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 122:&lt;/span&gt; &lt;span class="rem"&gt;-- INSERT new rows (not inserting identity column)&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 123:&lt;/span&gt; INSERT &lt;span class="kwrd"&gt;INTO&lt;/span&gt; tblToAudit (pkid, col0, col1, col2, col3, col4, col5, col6, &lt;/pre&gt;
&lt;pre class="alteven"&gt;                              col7, col8, col9, lastModified, lastModifiedBy)&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 124:&lt;/span&gt; &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; t.pkid, t.col0, t.col1, t.col2, t.col3, t.col4, t.col5, t.col6, &lt;/pre&gt;
&lt;pre class="alteven"&gt;             t.col7, t.col8, t.col9, t.modified, t.modifiedBy&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 125:&lt;/span&gt; &lt;span class="kwrd"&gt;FROM&lt;/span&gt; @tgtData &lt;span class="kwrd"&gt;AS&lt;/span&gt; t &lt;span class="kwrd"&gt;LEFT&lt;/span&gt; &lt;span class="kwrd"&gt;JOIN&lt;/span&gt; tblToAudit &lt;span class="kwrd"&gt;ON&lt;/span&gt; t.pkid = tblToAudit.pkid&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 126:&lt;/span&gt; &lt;span class="kwrd"&gt;WHERE&lt;/span&gt; tblToAudit.pkid &lt;span class="kwrd"&gt;IS&lt;/span&gt; &lt;span class="kwrd"&gt;NULL&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 127:&lt;/span&gt;  &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 128:&lt;/span&gt; &lt;span class="rem"&gt;-- reset identity insertion option&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 129:&lt;/span&gt; &lt;span class="kwrd"&gt;SET&lt;/span&gt; &lt;span class="kwrd"&gt;IDENTITY_INSERT&lt;/span&gt; tblToAudit &lt;span class="kwrd"&gt;OFF&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 130:&lt;/span&gt;  &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 131:&lt;/span&gt; &lt;span class="rem"&gt;-- Commit the transaction&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 132:&lt;/span&gt; &lt;span class="kwrd"&gt;COMMIT&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 133:&lt;/span&gt;  &lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 134:&lt;/span&gt; &lt;span class="kwrd"&gt;END&lt;/span&gt;&lt;/pre&gt;
&lt;pre class="alteven"&gt;&lt;span class="lnum"&gt; 135:&lt;/span&gt; GO&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I hope it is of some use to you.  If you want clarification of the code, or if you want to make a suggestion, please do send me a comment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Versions&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; 2008, &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; 2005 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Metadata&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Categories: IT Management, Software Development, &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; &lt;/li&gt;
    &lt;li&gt;Additional keywords: common table expressions, table variables, window functions, auditing, change tracking, column-level tracking &lt;/li&gt;
&lt;li&gt;Technorati Tags: 
&lt;a href="http://technorati.com/tags/SQL" rel="tag"&gt;SQL&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/SQL%20Server" rel="tag"&gt;SQL Server&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/auditing" rel="tag"&gt;auditing&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/change%20tracking" rel="tag"&gt;change tracking&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/column%20auditing" rel="tag"&gt;column%20auditing&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;img src="http://blogs.interakting.co.uk/steve/aggbug/221.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Stephen Horsfield</dc:creator>
            <guid>http://blogs.interakting.co.uk/steve/archive/2008/03/10/SQL-Server-Fun-with-column-level-auditing.aspx</guid>
            <pubDate>Mon, 10 Mar 2008 12:35:31 GMT</pubDate>
            <wfw:comment>http://blogs.interakting.co.uk/steve/comments/221.aspx</wfw:comment>
            <comments>http://blogs.interakting.co.uk/steve/archive/2008/03/10/SQL-Server-Fun-with-column-level-auditing.aspx#feedback</comments>
            <wfw:commentRss>http://blogs.interakting.co.uk/steve/comments/commentRss/221.aspx</wfw:commentRss>
        </item>
        <item>
            <title>SQL Server: Generating random data</title>
            <link>http://blogs.interakting.co.uk/steve/archive/2008/03/03/SQL-Server-Generating-random-data.aspx</link>
            <description>&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I've been looking at the performance of &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; 2008, following the recent SQL Bits conference (see &lt;a href="http://www.sqlbits.com"&gt;http://www.sqlbits.com&lt;/a&gt;), and have been looking at creating some test data for my analysis.  This is probably old stuff to most of you, but I hit an interesting issue...&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Context&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Specifically, I've been looking at the performance of the new filtered indexes support in 2008.  If you haven't come across these, I recommend that you have a look but I'll probably post about it in the next few days (no promises, though!).  To do the analysis I wanted a table with heavily weighted data so I thought I'd just generate it using the RAND() function.  Only, the RAND() function didn't seem to give particularly random data.&lt;/p&gt;
&lt;p&gt;Here's my original code:&lt;/p&gt;
&lt;div class="csharpcode-wrapper"&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; &lt;span class="kwrd"&gt;TOP&lt;/span&gt;(1000)
    RAND() &lt;span class="kwrd"&gt;AS&lt;/span&gt; [&lt;span class="kwrd"&gt;value&lt;/span&gt;]
    &lt;span class="kwrd"&gt;FROM&lt;/span&gt; master.sys.objects a, master.sys.objects b
GO&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;What results do you expect?  This is what I get, and I've checked the same occurs in &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; 2005:&lt;/p&gt;
&lt;p&gt;0.863657983742771 &lt;br /&gt;
0.863657983742771 &lt;br /&gt;
0.863657983742771 &lt;br /&gt;
0.863657983742771 &lt;br /&gt;
0.863657983742771 &lt;br /&gt;
0.863657983742771 &lt;br /&gt;
0.863657983742771 &lt;br /&gt;
...&lt;/p&gt;
&lt;p&gt;So, the RAND() function was only executed once for the query.  Not quite the random data I'd hoped for :(&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The most efficient solution when generating many rows is to use a table-valued CLR function to generate the row data.  For testing, I had two options:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;a temporary table &lt;/li&gt;
    &lt;li&gt;a table variable &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I cannot use the RAND() function within a table-valued user-defined function, so I had to use one of these approaches.  I'll show the table variable solution but the temporary table solution is virtually identical.  Here it is:&lt;/p&gt;
&lt;div class="csharpcode-wrapper"&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;BEGIN&lt;/span&gt;
    &lt;span class="kwrd"&gt;SET&lt;/span&gt; &lt;span class="kwrd"&gt;ROWCOUNT&lt;/span&gt; 0
    &lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @randomNumbers &lt;span class="kwrd"&gt;TABLE&lt;/span&gt; (&lt;span class="kwrd"&gt;value&lt;/span&gt; &lt;span class="kwrd"&gt;float&lt;/span&gt;)
    &lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @i &lt;span class="kwrd"&gt;int&lt;/span&gt;

    &lt;span class="kwrd"&gt;SET&lt;/span&gt; @i = 0
    &lt;span class="kwrd"&gt;WHILE&lt;/span&gt; @i &amp;lt; 1000
        &lt;span class="kwrd"&gt;BEGIN&lt;/span&gt;
            INSERT &lt;span class="kwrd"&gt;INTO&lt;/span&gt; @randomNumbers (&lt;span class="kwrd"&gt;value&lt;/span&gt;) &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; RAND()
            &lt;span class="kwrd"&gt;SET&lt;/span&gt; @i = @i + 1
        &lt;span class="kwrd"&gt;END&lt;/span&gt;
    &lt;span class="kwrd"&gt;SELECT&lt;/span&gt; * &lt;span class="kwrd"&gt;FROM&lt;/span&gt; @randomNumbers
&lt;span class="kwrd"&gt;END&lt;/span&gt;    
GO&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Of course you need to adjust this script for it to be useful :)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Versions&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; 2008 &lt;/li&gt;
    &lt;li&gt;&lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; 2005 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Metadata&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Categories: Software development, &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt;, Random Data &lt;/li&gt;
    &lt;li&gt;Additional keywords: RAND(), generate random data, test data &lt;/li&gt;
&lt;li&gt;Technorati Tags: 
&lt;a href="http://technorati.com/tags/SQL" rel="tag"&gt;SQL&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/SQL%20Server" rel="tag"&gt;SQL Server&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/random%20numbers" rel="tag"&gt;random%20numbers&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/RAND" rel="tag"&gt;RAND&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;img src="http://blogs.interakting.co.uk/steve/aggbug/216.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Stephen Horsfield</dc:creator>
            <guid>http://blogs.interakting.co.uk/steve/archive/2008/03/03/SQL-Server-Generating-random-data.aspx</guid>
            <pubDate>Mon, 03 Mar 2008 17:08:35 GMT</pubDate>
            <wfw:comment>http://blogs.interakting.co.uk/steve/comments/216.aspx</wfw:comment>
            <comments>http://blogs.interakting.co.uk/steve/archive/2008/03/03/SQL-Server-Generating-random-data.aspx#feedback</comments>
            <wfw:commentRss>http://blogs.interakting.co.uk/steve/comments/commentRss/216.aspx</wfw:commentRss>
        </item>
        <item>
            <title>SQL Server: Viewing Database Permissions Using a Query</title>
            <link>http://blogs.interakting.co.uk/steve/archive/2008/01/17/SQL-Server-Viewing-Database-Permissions-Using-a-Query.aspx</link>
            <description>&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Have you ever wondered how to view object permissions using a query?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Simple example&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Try this on a database:&lt;/p&gt;
&lt;code&gt;select o.name, u.name, p.permission_name, p.state, p.state_desc &lt;br /&gt;
  from sys.database_permissions p &lt;br /&gt;
       inner join sys.all_objects o &lt;br /&gt;
         on p.major_id = o.object_id &lt;br /&gt;
       inner join sys.database_principals u &lt;br /&gt;
         on p.grantee_principal_id = u.principal_id &lt;/code&gt;
&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;&lt;a title="http://technet.microsoft.com/en-us/library/ms188367.aspx" target="_blank" href="http://technet.microsoft.com/en-us/library/ms188367.aspx"&gt;sys.database_permissions (Microsoft TechNet)&lt;/a&gt;&lt;/strong&gt; &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;&lt;a title="http://technet.microsoft.com/en-us/library/ms187328.aspx" target="_blank" href="http://technet.microsoft.com/en-us/library/ms187328.aspx"&gt;sys.database_principals (Microsoft TechNet)&lt;/a&gt;&lt;/strong&gt; &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;&lt;a title="http://technet.microsoft.com/en-us/library/ms178618.aspx" target="_blank" href="http://technet.microsoft.com/en-us/library/ms178618.aspx"&gt;sys.all_objects (Microsoft TechNet)&lt;/a&gt;&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Versions&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Microsoft &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; 2005 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Metadata&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Categories: &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt;, IT Management, Software Development, Security &lt;/li&gt;
    &lt;li&gt;Additional keywords: information schema, system views, permissions, security, query &lt;/li&gt;
&lt;li&gt;Technorati Tags: 
&lt;a href="http://technorati.com/tags/SQL" rel="tag"&gt;SQL&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/SQL%20Server" rel="tag"&gt;SQL Server&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/software%20development" rel="tag"&gt;software development&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/IT%20Management" rel="tag"&gt;IT Management&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/security" rel="tag"&gt;security&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/permissions" rel="tag"&gt;permissions&lt;/a&gt;
&lt;/li&gt;&lt;/ul&gt;&lt;img src="http://blogs.interakting.co.uk/steve/aggbug/175.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Stephen Horsfield</dc:creator>
            <guid>http://blogs.interakting.co.uk/steve/archive/2008/01/17/SQL-Server-Viewing-Database-Permissions-Using-a-Query.aspx</guid>
            <pubDate>Thu, 17 Jan 2008 09:02:18 GMT</pubDate>
            <wfw:comment>http://blogs.interakting.co.uk/steve/comments/175.aspx</wfw:comment>
            <comments>http://blogs.interakting.co.uk/steve/archive/2008/01/17/SQL-Server-Viewing-Database-Permissions-Using-a-Query.aspx#feedback</comments>
            <wfw:commentRss>http://blogs.interakting.co.uk/steve/comments/commentRss/175.aspx</wfw:commentRss>
        </item>
        <item>
            <title>SQL Server: Primer &amp;mdash; Using Alias Data Types for Simpler Database Design</title>
            <link>http://blogs.interakting.co.uk/steve/archive/2008/01/15/SQL-Server-Primer-mdash-Using-Alias-Data-Types-for-Simpler.aspx</link>
            <description>&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Do you have lots of tables that reference each other using ID fields?  Are they of different types, such as nvarchar(10) and int?  Do you sometimes find that it can be hard to remember how many characters should be in the type?  And what about when you decide that you need a bigger type, such as nvarchar(30)?  How can you ensure that your database design remains consistent?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alias Data Types and the CREATE TYPE statement&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An &lt;em&gt;Alias Data Type&lt;/em&gt; is a type defined by you in &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; that refers to an internal data type.  You can use it anywhere you would use a normal SQL type, except in table variables.  For example, the following command declares adtCustomerID as a non-NULL nvarchar(10):&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CREATE TYPE [dbo].[adtCustomerID] &lt;br /&gt;
  FROM nvarchar(10) &lt;br /&gt;
  NOT NULL&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You can then use the type adtCustomerID in your table definitions and stored procedures, never worrying about how many characters are in the type:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CREATE TABLE [dbo].[customers] ( &lt;br /&gt;
  customer_id     adtCustomerID  PRIMARY KEY, &lt;br /&gt;
  customer_name   nvarchar(100), &lt;br /&gt;
  ... )&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CREATE TABLE [dbo].[orders] ( &lt;br /&gt;
  order_id        adtOrderID     PRIMARY KEY, &lt;br /&gt;
  customer_id     adtCustomerID, &lt;br /&gt;
  ... )&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Caveat&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You cannot change a type while it is in use.  This means that you will have to change stored procedures (etc.) and tables to use the underlying data type before changing it, so type definitions are most useful when they are not going to change, such as in key fields.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a title="http://technet.microsoft.com/en-us/library/ms175007.aspx" target="_blank" href="http://technet.microsoft.com/en-us/library/ms175007.aspx"&gt;CREATE TYPE (Microsoft TechNet)&lt;/a&gt; &lt;/li&gt;
    &lt;li&gt;&lt;a title="http://technet.microsoft.com/en-us/library/ms189283.aspx" target="_blank" href="http://technet.microsoft.com/en-us/library/ms189283.aspx"&gt;Working with Alias Data Types (Microsoft TechNet)&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Versions&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Microsoft &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; 2005 &lt;/li&gt;
    &lt;li&gt;Microsoft &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; 2000 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Metadata&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Categories: &lt;a href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt;, IT Management, Software Development, Alias Data Types &lt;/li&gt;
    &lt;li&gt;Additional keywords: simplicity, CREATE TYPE, user defined type, best practice, primer &lt;/li&gt;
&lt;li&gt;Technorati Tags: 
&lt;a href="http://technorati.com/tags/SQL" rel="tag"&gt;SQL&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/SQL%20Server" rel="tag"&gt;SQL Server&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/software%20development" rel="tag"&gt;software development&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/architecture" rel="tag"&gt;architecture&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/database%20design" rel="tag"&gt;database design&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/CREATE%20TYPE" rel="tag"&gt;CREATE TYPE&lt;/a&gt;
&lt;/li&gt;&lt;/ul&gt;&lt;img src="http://blogs.interakting.co.uk/steve/aggbug/166.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Stephen Horsfield</dc:creator>
            <guid>http://blogs.interakting.co.uk/steve/archive/2008/01/15/SQL-Server-Primer-mdash-Using-Alias-Data-Types-for-Simpler.aspx</guid>
            <pubDate>Tue, 15 Jan 2008 11:00:09 GMT</pubDate>
            <wfw:comment>http://blogs.interakting.co.uk/steve/comments/166.aspx</wfw:comment>
            <comments>http://blogs.interakting.co.uk/steve/archive/2008/01/15/SQL-Server-Primer-mdash-Using-Alias-Data-Types-for-Simpler.aspx#feedback</comments>
            <wfw:commentRss>http://blogs.interakting.co.uk/steve/comments/commentRss/166.aspx</wfw:commentRss>
        </item>
        <item>
            <title>SQL Server: Primer &amp;mdash; Using Database Views</title>
            <link>http://blogs.interakting.co.uk/steve/archive/2008/01/14/SQL-Server-Primer-mdash-Using-Database-Views.aspx</link>
            <description>&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I'm in the process of reviewing a large number of SQL stored procedures, and occasionally I find that a table join isn't properly expressed.  This can result in hard to find bugs, but how can you make your SQL code simpler?  Isn't using a stored procedure best-practice anyway?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It is quite common to have a series of related tables.  Consider a stock control system.  Perhaps you have tables for customers, orders, order lines and stock items.  In this simple scenario, you have four tables and clearly there are joins between several of the tables.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Common solution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A common solution is to describe the table joins in SQL code in each stored procedure.  This way the database is only loosely-coupled to the application code, but the stored procedure is tightly-coupled to the database schema.  Suppose the stored procedure returns the number of times a customer has ordered each of the items he/she has ordered.  The SQL might look like the following&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SELECT items.item_description, COUNT(order_lines.item_id) &lt;br /&gt;
FROM         customers &lt;br /&gt;
  INNER JOIN orders ON customers.customer_id = orders.customer_id &lt;br /&gt;
  INNER JOIN order_lines ON orders.order_id = order_lines.order_id &lt;br /&gt;
  INNER JOIN items ON order_lines.item_id = items.item_id &lt;br /&gt;
WHERE customers.customer_name = @customer_name &lt;br /&gt;
GROUP BY order_lines.item_id, items.description&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This isn't wrong, but it is easy to forget a join field, particularly where a join is on multiple fields, and you have to remember the joins every time.  Essentially, the stored procedure encompasses both schema knowledge and business logic.  Wouldn't it be better to separate the two?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A better solution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;font color="#3366ff"&gt;&lt;strong&gt;Key Point: Views allow you to express table relationships without affecting server performance.  They allow for reusable table expressions.&lt;/strong&gt;&lt;/font&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let's build some useful views, getting closer to our original SQL as we go along:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: I've omitted several columns in the resulting view for brevity, but there is no reason to do so unless you are also using a clustered index on the view (in which case the columns determine the size of the index)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CREATE VIEW all_customer_orders AS &lt;br /&gt;
  SELECT customers.customer_name, orders.* &lt;br /&gt;
&lt;/code&gt;&lt;code&gt;    FROM customers INNER JOIN orders &lt;br /&gt;
      ON customers.customer_id = orders.customer_id&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CREATE VIEW all_customer_lines AS &lt;br /&gt;
  SELECT customer_id, customer_name, order_lines.* &lt;br /&gt;
    FROM all_customer_orders INNER JOIN order_lines &lt;br /&gt;
      ON all_customer_orders.order_id = order_lines.order_id&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CREATE VIEW all_line_item_details AS &lt;br /&gt;
  SELECT order_lines.order_line_id, &lt;br /&gt;
         order_lines.item_id, &lt;br /&gt;
         items.item_description &lt;br /&gt;
    FROM order_lines INNER JOIN items &lt;br /&gt;
      ON order_lines.item_id = items.item_id&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CREATE VIEW all_customer_line_item_details AS &lt;br /&gt;
  SELECT all_customer_lines.*, &lt;br /&gt;
         all_line_item_details.item_description &lt;br /&gt;
    FROM all_customer_lines INNER JOIN all_line_item_details &lt;br /&gt;
      ON all_customer_lines.order_line_id = all_line_item_details.order_line_id&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Finally, the stored procedure code can be expressed as&lt;/p&gt;
&lt;code&gt;SELECT item_description, COUNT(item_id) &lt;br /&gt;
  FROM all_customer_line_item_details &lt;br /&gt;
GROUP BY item_id, item_description &lt;br /&gt;
WHERE customer_name = @customer_name &lt;br /&gt;
&lt;/code&gt;
&lt;p&gt;This SQL no longer assumes any underlying knowledge of the table structure, it simply refers to a single view.  Also, each view only has a single join and is therefore much easier to both test and understand.&lt;/p&gt;
&lt;p&gt;Which views to create is a matter of experience, but whenever you are joining several tables, consider using a view.  The view is easier to maintain and easier to test.  It is also reusable.  If you need to parameterise the view, consider using a table-valued function to represent it.&lt;/p&gt;
&lt;p&gt;Remember as well that views can be indexed.  This can introduce significant performance benefits without the penalties of denormalised data.  The indexes aren't free from a performance perspective, but judicious use can result in optimal and simple queries.&lt;/p&gt;
&lt;p&gt;If you are using &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; 2005 Enterprise edition you also get the benefit of having the &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; optimiser consider your views (and the associated indices) automatically.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a title="http://msdn2.microsoft.com/en-us/library/ms190706.aspx" target="_blank" href="http://msdn2.microsoft.com/en-us/library/ms190706.aspx"&gt;Views (Database Engine) (Microsoft MSDN)&lt;/a&gt; &lt;/li&gt;
    &lt;li&gt;&lt;a title="http://msdn2.microsoft.com/en-us/library/ms188250.aspx" target="_blank" href="http://msdn2.microsoft.com/en-us/library/ms188250.aspx"&gt;Scenarios for Using Views (Microsoft MSDN)&lt;/a&gt; &lt;/li&gt;
    &lt;li&gt;&lt;a title="http://msdn2.microsoft.com/en-us/library/ms191432.aspx" target="_blank" href="http://msdn2.microsoft.com/en-us/library/ms191432.aspx"&gt;Creating Indexed Views (Microsoft MSDN)&lt;/a&gt; &lt;/li&gt;
    &lt;li&gt;&lt;a title="http://msdn2.microsoft.com/en-us/library/ms191165.aspx" target="_blank" href="http://msdn2.microsoft.com/en-us/library/ms191165.aspx"&gt;Table-valued User-defined Functions (Microsoft MSDN)&lt;/a&gt; &lt;/li&gt;
    &lt;li&gt;&lt;a title="http://www.microsoft.com/technet/prodtechnol/sql/2005/impprfiv.mspx" href="http://www.microsoft.com/technet/prodtechnol/sql/2005/impprfiv.mspx"&gt;Improving Performance with SQL Server 2005 Indexed Views (Microsoft TechNet)&lt;/a&gt; (NEW!) &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Versions&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Microsoft &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; 2005 &lt;/li&gt;
    &lt;li&gt;Microsoft &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; 2000 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Metadata&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Categories: &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt;, IT Management, Software Development, Views, Stored Procedures  &lt;/li&gt;
    &lt;li&gt;Additional keywords: simplicity, queries, clustered index, views, best practice, primer, user-defined functions, UDF  &lt;/li&gt;
&lt;li&gt;Technorati Tags: 
&lt;a href="http://technorati.com/tags/SQL" rel="tag"&gt;SQL&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/SQL%20Server" rel="tag"&gt;SQL Server&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/software%20development" rel="tag"&gt;software development&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/architecture" rel="tag"&gt;architecture&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/database%20design" rel="tag"&gt;database design&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/database%20views" rel="tag"&gt;database views&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/best%20practice" rel="tag"&gt;best practice&lt;/a&gt;
&lt;/li&gt;&lt;/ul&gt;&lt;img src="http://blogs.interakting.co.uk/steve/aggbug/163.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Stephen Horsfield</dc:creator>
            <guid>http://blogs.interakting.co.uk/steve/archive/2008/01/14/SQL-Server-Primer-mdash-Using-Database-Views.aspx</guid>
            <pubDate>Mon, 14 Jan 2008 15:25:02 GMT</pubDate>
            <wfw:comment>http://blogs.interakting.co.uk/steve/comments/163.aspx</wfw:comment>
            <comments>http://blogs.interakting.co.uk/steve/archive/2008/01/14/SQL-Server-Primer-mdash-Using-Database-Views.aspx#feedback</comments>
            <wfw:commentRss>http://blogs.interakting.co.uk/steve/comments/commentRss/163.aspx</wfw:commentRss>
        </item>
        <item>
            <title>SQL Server: Missing Indices and a Helpful Database</title>
            <link>http://blogs.interakting.co.uk/steve/archive/2008/01/11/SQL-Server-Missing-Indices-and-a-Helpful-Database.aspx</link>
            <description>&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I've just found this by chance while in the process of reviewing around 100 stored procedures to check for performance efficiency.  It doesn't help with badly written queries, but if the query is reasonable then it can help you find those elusive missing indices.  Thought I'd have to find them myself, but thankfully help is at hand...&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Details&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Normally, you would use the &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; Profiler to help track performance issues, but what if you are not in a live environment?  &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; 2005 helpfully collects some data that you can use.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Warning:  the data is only kept for while the server is running.  If it restarts, you'll have to wait for it to recalculate the information!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The database automatically collects information on indices that it would have used had they existed.  You can use this to investigate whether creating the index is warranted.  &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; also lets you know its potential value.  This feature is on by default.&lt;/p&gt;
&lt;p&gt;Have a look at the references section for more details on the feature, but here is an example script to show you some of the information that is available:&lt;/p&gt;
&lt;code&gt;USE [master] &lt;br /&gt;
GO &lt;br /&gt;
&lt;br /&gt;
SELECT statement, avg_total_user_cost, avg_user_impact, statement, &lt;br /&gt;
equality_columns, inequality_columns, included_columns &lt;br /&gt;
FROM sys.dm_db_missing_index_group_stats a INNER JOIN sys.dm_db_missing_index_groups b ON a.group_handle = b.index_group_handle INNER JOIN &lt;br /&gt;
sys.dm_db_missing_index_details c ON b.index_handle = c.index_handle &lt;br /&gt;
ORDER BY avg_user_impact DESC &lt;br /&gt;
GO&lt;/code&gt;
&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a title="http://technet.microsoft.com/en-us/library/ms345417.aspx" target="_blank" href="http://technet.microsoft.com/en-us/library/ms345417.aspx"&gt;Finding Missing Indexes (Microsoft TechNet)&lt;/a&gt; &lt;/li&gt;
    &lt;li&gt;&lt;a title="http://technet.microsoft.com/en-us/library/ms345524.aspx" target="_blank" href="http://technet.microsoft.com/en-us/library/ms345524.aspx"&gt;About the Missing Indexes Feature (Microsoft TechNet)&lt;/a&gt; &lt;/li&gt;
    &lt;li&gt;&lt;a title="http://technet.microsoft.com/en-us/library/ms345405.aspx" target="_blank" href="http://technet.microsoft.com/en-us/library/ms345405.aspx"&gt;Using Missing Index Information to Write CREATE INDEX Statements (Microsoft TechNet)&lt;/a&gt; &lt;/li&gt;
    &lt;li&gt;&lt;a title="http://technet.microsoft.com/en-us/library/ms345485.aspx" target="_blank" href="http://technet.microsoft.com/en-us/library/ms345485.aspx"&gt;Limitations for Using the Missing Indexes Feature (Microsoft TechNet)&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Versions&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Microsoft &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; 2005 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Metadata&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Categories: &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt;, IT Management, Software Development, Performance Analysis &lt;/li&gt;
    &lt;li&gt;Additional keywords: query optimisation, missing indexes, statistics, tuning &lt;/li&gt;
&lt;li&gt;Technorati Tags: 
&lt;a href="http://technorati.com/tags/SQL" rel="tag"&gt;SQL&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/SQL%20Server" rel="tag"&gt;SQL Server&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/performance" rel="tag"&gt;performance&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/database%20indexing" rel="tag"&gt;database indexing&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/IT%20Management" rel="tag"&gt;IT Management&lt;/a&gt;
&lt;/li&gt;&lt;/ul&gt;&lt;img src="http://blogs.interakting.co.uk/steve/aggbug/159.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Stephen Horsfield</dc:creator>
            <guid>http://blogs.interakting.co.uk/steve/archive/2008/01/11/SQL-Server-Missing-Indices-and-a-Helpful-Database.aspx</guid>
            <pubDate>Fri, 11 Jan 2008 17:39:38 GMT</pubDate>
            <wfw:comment>http://blogs.interakting.co.uk/steve/comments/159.aspx</wfw:comment>
            <comments>http://blogs.interakting.co.uk/steve/archive/2008/01/11/SQL-Server-Missing-Indices-and-a-Helpful-Database.aspx#feedback</comments>
            <wfw:commentRss>http://blogs.interakting.co.uk/steve/comments/commentRss/159.aspx</wfw:commentRss>
        </item>
        <item>
            <title>SQL Server: Primer &amp;mdash; Foreign Key Constraints</title>
            <link>http://blogs.interakting.co.uk/steve/archive/2008/01/11/SQL-Server-Primer-mdash-Foreign-Key-Constraints.aspx</link>
            <description>&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You may know that it is best practice to create FOREIGN KEY constraints on related tables in a database.  Why is this?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Details&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A foreign key constraint can be used for:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Ensuring referential integrity: a foreign key constraint can ensure database consistency &lt;/li&gt;
    &lt;li&gt;Self-documenting databases: the foreign key clause can be viewed in the database and describes a relationship &lt;/li&gt;
    &lt;li&gt;Efficiency: even without an index, the &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; engine recognises the constraint and uses it to optimise table joins automatically &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Generally, you will also want to index the foreign key columns in the table, but it is worth adding the FOREIGN KEY constraint even if you don't create the index because it helps the database to optimise the execution plan.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a title="http://technet.microsoft.com/en-us/library/aa933117(sql.80).aspx" target="_blank" href="http://technet.microsoft.com/en-us/library/aa933117(sql.80).aspx"&gt;FOREIGN KEY Constraints (Microsoft TechNet)&lt;/a&gt; &lt;/li&gt;
    &lt;li&gt;&lt;a title="http://technet.microsoft.com/en-us/library/aa213233(SQL.80).aspx" target="_blank" href="http://technet.microsoft.com/en-us/library/aa213233(SQL.80).aspx"&gt;Join Fundamentals (Microsoft TechNet)&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Versions&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Microsoft &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; 2005 &lt;/li&gt;
    &lt;li&gt;Microsoft &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; 2000 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Metadata&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Categories: &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt;, IT Management, Software Development, Performance Analysis &lt;/li&gt;
    &lt;li&gt;Additional keywords: query optimisation, table join, foreign key, primary key, primer &lt;/li&gt;
&lt;li&gt;Technorati Tags: 
&lt;a href="http://technorati.com/tags/SQL" rel="tag"&gt;SQL&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/SQL%20Server" rel="tag"&gt;SQL Server&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/performance" rel="tag"&gt;performance&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/database%20indexing" rel="tag"&gt;database indexing&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/architecture" rel="tag"&gt;architecture&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/database%20design" rel="tag"&gt;database design&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/IT%20Management" rel="tag"&gt;IT Management&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/best%20practice" rel="tag"&gt;best practice&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/software%20development" rel="tag"&gt;software development&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/foreign%20keys" rel="tag"&gt;foreign keys&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/referential%20integrity" rel="tag"&gt;referential integrity&lt;/a&gt;
&lt;/li&gt;&lt;/ul&gt;&lt;img src="http://blogs.interakting.co.uk/steve/aggbug/158.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Stephen Horsfield</dc:creator>
            <guid>http://blogs.interakting.co.uk/steve/archive/2008/01/11/SQL-Server-Primer-mdash-Foreign-Key-Constraints.aspx</guid>
            <pubDate>Fri, 11 Jan 2008 15:31:07 GMT</pubDate>
            <wfw:comment>http://blogs.interakting.co.uk/steve/comments/158.aspx</wfw:comment>
            <comments>http://blogs.interakting.co.uk/steve/archive/2008/01/11/SQL-Server-Primer-mdash-Foreign-Key-Constraints.aspx#feedback</comments>
            <wfw:commentRss>http://blogs.interakting.co.uk/steve/comments/commentRss/158.aspx</wfw:commentRss>
        </item>
        <item>
            <title>SQL Server: Which tables are storing the most physical data?</title>
            <link>http://blogs.interakting.co.uk/steve/archive/2008/01/09/SQL-Server-Which-tables-are-storing-the-most-physical-data.aspx</link>
            <description>&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Following on from my previous post, here's a quick way to find out which data tables are using the most disk space...&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;a title="http://msdn2.microsoft.com/en-us/library/ms189051.aspx" target="_blank" href="http://msdn2.microsoft.com/en-us/library/ms189051.aspx"&gt;Table and Index Organization (Microsoft SQL Server 2005 Books Online (September 2007))&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;
&lt;code&gt;SELECT o.name AS table_name, SUM(au.data_pages) as pages, &lt;br /&gt;
       ((SUM(au.data_pages) * 8192) / (1024 * 1024)) AS MB &lt;br /&gt;
FROM sys.allocation_units AS au &lt;br /&gt;
     JOIN sys.partitions AS p ON au.container_id = p.partition_id &lt;br /&gt;
     JOIN sys.tables AS o ON p.object_id = o.object_id &lt;br /&gt;
     JOIN sys.indexes AS i ON p.index_id = i.index_id AND i.object_id = p.object_id &lt;br /&gt;
GROUP BY o.name &lt;br /&gt;
ORDER BY pages desc, o.name &lt;br /&gt;
GO &lt;/code&gt;
&lt;p&gt;&lt;strong&gt;Versions&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Microsoft &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt; 2005 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Metadata&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Categories: &lt;a title="Microsoft SQL Server" target="_blank" href="http://www.microsoft.com/sql/default.mspx"&gt;SQL Server&lt;/a&gt;, Performance Analysis &lt;/li&gt;
    &lt;li&gt;Additional keywords: table statistics, index statistics, optimisation, performance &lt;/li&gt;
&lt;li&gt;Technorati Tags: 
&lt;a href="http://technorati.com/tags/IT%20Management" rel="tag"&gt;IT Management&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/SQL" rel="tag"&gt;SQL&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/SQL%20Server" rel="tag"&gt;SQL Server&lt;/a&gt;,
&lt;a href="http://technorati.com/tags/storage" rel="tag"&gt;storage&lt;/a&gt;
&lt;/li&gt;&lt;/ul&gt;&lt;img src="http://blogs.interakting.co.uk/steve/aggbug/151.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Stephen Horsfield</dc:creator>
            <guid>http://blogs.interakting.co.uk/steve/archive/2008/01/09/SQL-Server-Which-tables-are-storing-the-most-physical-data.aspx</guid>
            <pubDate>Wed, 09 Jan 2008 15:16:07 GMT</pubDate>
            <wfw:comment>http://blogs.interakting.co.uk/steve/comments/151.aspx</wfw:comment>
            <comments>http://blogs.interakting.co.uk/steve/archive/2008/01/09/SQL-Server-Which-tables-are-storing-the-most-physical-data.aspx#feedback</comments>
            <wfw:commentRss>http://blogs.interakting.co.uk/steve/comments/commentRss/151.aspx</wfw:commentRss>
        </item>
    </channel>
</rss>