<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://sqlservernation.com/utility/FeedStylesheets/atom.xsl" media="screen"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><title type="html">Tip of the Week</title><subtitle type="html">Check in here weekly for the latest SQL Server tip from SQL Server Nation</subtitle><id>http://sqlservernation.com/blogs/tipweek/atom.aspx</id><link rel="alternate" type="text/html" href="http://sqlservernation.com/blogs/tipweek/default.aspx" /><link rel="self" type="application/atom+xml" href="http://sqlservernation.com/blogs/tipweek/atom.aspx" /><generator uri="http://communityserver.org" version="4.1.40407.4157">Community Server</generator><updated>2009-02-25T10:52:00Z</updated><entry><title>SQL Server Triggers</title><link rel="alternate" type="text/html" href="/blogs/tipweek/archive/2010/02/28/handling-trigger-logic.aspx" /><id>/blogs/tipweek/archive/2010/02/28/handling-trigger-logic.aspx</id><published>2010-03-01T01:44:00Z</published><updated>2010-03-01T01:44:00Z</updated><content type="html">&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;</content><author><name>TimChapman</name><uri>http://sqlservernation.com/members/TimChapman/default.aspx</uri></author><category term="SQL Server" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/SQL+Server/default.aspx" /><category term="trigger" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/trigger/default.aspx" /><category term="loop" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/loop/default.aspx" /><category term="INSERTED" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/INSERTED/default.aspx" /><category term="CURSOR" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/CURSOR/default.aspx" /><category term="logic" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/logic/default.aspx" /><category term="DML" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/DML/default.aspx" /><category term="DELETED" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/DELETED/default.aspx" /></entry><entry><title>What version of SQL Server am I running?</title><link rel="alternate" type="text/html" href="/blogs/tipweek/archive/2009/11/01/using-the-serverproperty-function.aspx" /><id>/blogs/tipweek/archive/2009/11/01/using-the-serverproperty-function.aspx</id><published>2009-11-01T15:03:00Z</published><updated>2009-11-01T15:03:00Z</updated><content type="html">&lt;p&gt;Quite often I see the question in the SQL Server forums &amp;quot;What version of SQL Server am I running?&amp;quot;&amp;nbsp; To be honest, this is a VERY easy question to answer.&amp;nbsp; Microsoft provides a function with a set of properties you can query for identifying such information.&amp;nbsp; The function provided by Microsoft is SERVERPROPERTY.&amp;nbsp; However, there are specific parameters you need to pass to it to get the information you need.&amp;nbsp; So, rather than having to worry about these specific parameter values, I&amp;#39;ve created a &lt;a title="Create your own System Stored Procedure" href="http://sqlservernation.com/blogs/howtos/archive/2009/10/29/create-a-system-stored-procedure.aspx"&gt;system stored procedure&lt;/a&gt; that you can use to find out any information you need to about your SQL Server instance without having to know specific parameter values.&amp;nbsp; The code for the system stored procedure is below.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;line-height:14px;background-color:#eeeeee;width:100%;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;font-size:12px;"&gt;&lt;code&gt;USE master&lt;br /&gt;GO&lt;br /&gt;CREATE PROCEDURE sp_ServerProperties&lt;br /&gt;AS&lt;br /&gt; SELECT &lt;br /&gt;     BuildClrVersion = SERVERPROPERTY(&amp;#39;BuildClrVersion&amp;#39;),&lt;br /&gt;     Collation = SERVERPROPERTY(&amp;#39;Collation&amp;#39;),&lt;br /&gt;     CollationID = SERVERPROPERTY(&amp;#39;CollationID&amp;#39;),&lt;br /&gt;     ComparisonStyle = SERVERPROPERTY(&amp;#39;ComparisonStyle&amp;#39;),&lt;br /&gt;     ComputerNamePhysicalNetBIOS = SERVERPROPERTY(&amp;#39;ComputerNamePhysicalNetBIOS&amp;#39;),&lt;br /&gt;     Edition = SERVERPROPERTY(&amp;#39;Edition&amp;#39;),&lt;br /&gt;     EditionID = SERVERPROPERTY(&amp;#39;EditionID&amp;#39;),&lt;br /&gt;     EngineEdition = SERVERPROPERTY(&amp;#39;EngineEdition&amp;#39;),&lt;br /&gt;     InstanceName = SERVERPROPERTY(&amp;#39;InstanceName&amp;#39;),&lt;br /&gt;     IsClustered = SERVERPROPERTY(&amp;#39;IsClustered&amp;#39;),&lt;br /&gt;     IsFullTextInstalled = SERVERPROPERTY(&amp;#39;IsFullTextInstalled&amp;#39;),&lt;br /&gt;     IsIntegratedSecurityOnly = SERVERPROPERTY(&amp;#39;IsIntegratedSecurityOnly&amp;#39;),&lt;br /&gt;     IsSingleUser = SERVERPROPERTY(&amp;#39;IsSingleUser&amp;#39;),&lt;br /&gt;     LCID = SERVERPROPERTY(&amp;#39;LCID&amp;#39;),&lt;br /&gt;     LicenseType = SERVERPROPERTY(&amp;#39;LicenseType&amp;#39;),&lt;br /&gt;     MachineName = SERVERPROPERTY(&amp;#39;MachineName&amp;#39;),&lt;br /&gt;     NumLicenses = SERVERPROPERTY(&amp;#39;NumLicenses&amp;#39;),&lt;br /&gt;     ProcessID = SERVERPROPERTY(&amp;#39;ProcessID&amp;#39;),&lt;br /&gt;     ProductVersion = SERVERPROPERTY(&amp;#39;ProductVersion&amp;#39;),&lt;br /&gt;     ProductLevel = SERVERPROPERTY(&amp;#39;ProductLevel&amp;#39;),&lt;br /&gt;     ResourceLastUpdateDateTime = SERVERPROPERTY(&amp;#39;ResourceLastUpdateDateTime&amp;#39;),&lt;br /&gt;     ResourceVersion = SERVERPROPERTY(&amp;#39;ResourceVersion&amp;#39;),&lt;br /&gt;     ServerName = SERVERPROPERTY(&amp;#39;ServerName&amp;#39;),&lt;br /&gt;     SqlCharSet = SERVERPROPERTY(&amp;#39;SqlCharSet&amp;#39;),&lt;br /&gt;     SqlCharSet = SERVERPROPERTY(&amp;#39;SqlCharSetName&amp;#39;),&lt;br /&gt;     SqlSortOrder = SERVERPROPERTY(&amp;#39;SqlSortOrder&amp;#39;),&lt;br /&gt;     SqlSortOrderName = SERVERPROPERTY(&amp;#39;SqlSortOrderName&amp;#39;),&lt;br /&gt;     FilestreamShareName = SERVERPROPERTY(&amp;#39;FilestreamShareName&amp;#39;),&lt;br /&gt;     FilestreamConfiguredLevel = SERVERPROPERTY(&amp;#39;FilestreamConfiguredLevel&amp;#39;),&lt;br /&gt;     FilestreamEffectiveLevel = SERVERPROPERTY(&amp;#39;FilestreamEffectiveLevel&amp;#39;)&lt;br /&gt;     &lt;br /&gt;GO&lt;br /&gt;use master&lt;br /&gt;go&lt;br /&gt;EXECUTE sp_ms_marksystemobject &amp;#39;sp_ServerProperties&amp;#39;&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;Once you&amp;#39;ve created the procedure, you can execute it in the context of any database on your SQL Server instance to return all of the system properties for your SQL Server instance.&amp;nbsp; Note that this procedure is customized for SQL Server 2008.&amp;nbsp; The 2005 version will be absent a few of these properties...namely the FILESTREAM properties.&lt;/p&gt;
&lt;p&gt;HTH,&lt;/p&gt;
&lt;p&gt;Tim&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://sqlservernation.com/aggbug.aspx?PostID=313" width="1" height="1"&gt;</content><author><name>TimChapman</name><uri>http://sqlservernation.com/members/TimChapman/default.aspx</uri></author></entry><entry><title>Why is this query slow: 1</title><link rel="alternate" type="text/html" href="/blogs/tipweek/archive/2009/10/24/why-is-this-query-slow-1.aspx" /><id>/blogs/tipweek/archive/2009/10/24/why-is-this-query-slow-1.aspx</id><published>2009-10-24T13:41:00Z</published><updated>2009-10-24T13:41:00Z</updated><content type="html">&lt;p&gt;Since this is the first part of this series, I&amp;#39;ll give a little background as to what I want to accomplish.&amp;nbsp; At some point, you&amp;#39;re going to run into a situation where a query on your SQL Server system is &amp;quot;slow&amp;quot;.&amp;nbsp; Slowness is usually ambiguous, and almost always relates to a persons reference.&amp;nbsp; However, when we hear that something is slow or broken we need to check it out and see what may be the cause.&amp;nbsp; So, my goal is to introduce simple to increasingly complex queries and show you the steps I like to take to diagnose what may be causing them to be &amp;quot;slow&amp;quot;.&lt;/p&gt;
&lt;p&gt;Statement 1:&lt;/p&gt;
&lt;p&gt;SELECT *&lt;br /&gt;FROM TableA a&lt;br /&gt;JOIN TableB b ON a.PrimaryKeyField = b.ForeignKeyField&lt;br /&gt;WHERE b.LookupValue = 3&lt;/p&gt;
&lt;p&gt;The first thing I like to look for are the simple things.&amp;nbsp; 9 times out of 10 something simple is being missed and causing poor performance.&amp;nbsp; So, in the query above I first want to look for any columns that may need to be indexed.&amp;nbsp; My TableA table has a clustered index (default) on the Primary Key field (named PrimaryKeyField), so single record lookups should perform seek operations.&amp;nbsp; Next I want to look at the ForeignKeyField in TableB.&amp;nbsp; Since I am joining on this field back to the PrimaryKeyField in TableA I want to make sure this field has a nonclustered index on it to facilitate faster lookup operations.&amp;nbsp; If there is indeed a foreign key constraint defined between these two tables based on this relationship, the nonclustered index on the ForeignKeyField in TableB becomes even more important.&amp;nbsp; SQL Server has to perform a lookup check to make sure the constraint is satistified anytime a record is inserted (or the ForeignKeyField is updated) in the table, so if SQL Server can use an index to perform the seek it is all the better.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;Next I want to check the criteria in the WHERE statement.&amp;nbsp; &lt;strong&gt;Note&lt;/strong&gt;:&amp;nbsp; The criteria in the JOIN operations can be thought of as &amp;quot;criteria&amp;quot;, so anytime you&amp;#39;re considering criteria you should also be considering indexes to quickly satisfy the criteria.&amp;nbsp; Because I am doing a lookup on the LookupValue field, I&amp;#39;ll want to see if an index on this field makes sense.&amp;nbsp; Some indexes make sense, some do not....more on that later.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;I also want to remove the SELECT * portion of the query and replace it with a specified field list.&amp;nbsp; Bringing back all of the fields involved almost always results in a slower query, and can end up breaking your data layer or interface code if you&amp;#39;re not careful.&lt;/p&gt;
&lt;p&gt;Lastly, I want to make sure my table references are prefixed with the schema that the table belongs to.&amp;nbsp; I like to do this so that SQL Server doesn&amp;#39;t have to do the determination for me.&lt;/p&gt;
&lt;p&gt;I plan on posting more of these in the future.&amp;nbsp; If you like them and/or have any suggestions that you would like for me to go over, please let me know!&lt;/p&gt;
&lt;p&gt;Tim&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://sqlservernation.com/aggbug.aspx?PostID=309" width="1" height="1"&gt;</content><author><name>TimChapman</name><uri>http://sqlservernation.com/members/TimChapman/default.aspx</uri></author><category term="JOIN" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/JOIN/default.aspx" /><category term="SQL Server" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/SQL+Server/default.aspx" /><category term="performance" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/performance/default.aspx" /><category term="default" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/default/default.aspx" /><category term="index tuning" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/index+tuning/default.aspx" /><category term="nonclustered index" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/nonclustered+index/default.aspx" /><category term="primary key" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/primary+key/default.aspx" /><category term="WHERE" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/WHERE/default.aspx" /><category term="clustered index" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/clustered+index/default.aspx" /><category term="foreign key" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/foreign+key/default.aspx" /><category term="poor performance" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/poor+performance/default.aspx" /><category term="criteria" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/criteria/default.aspx" /><category term="fast query" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/fast+query/default.aspx" /></entry><entry><title>Viewing Performance Counters in SQL Server 2005</title><link rel="alternate" type="text/html" href="/blogs/tipweek/archive/2009/09/30/viewing-performance-counters-in-sql-server-2005.aspx" /><id>/blogs/tipweek/archive/2009/09/30/viewing-performance-counters-in-sql-server-2005.aspx</id><published>2009-10-01T02:21:00Z</published><updated>2009-10-01T02:21:00Z</updated><content type="html">&lt;p&gt;I like Performance Monitor (perfmon); its a good tool for diagnosing many different types of problems.&amp;nbsp; Its great for determining from how well your memory is handling requests to how well your CPU usage is doing...its also great for checking out different SQL Server metrics.&amp;nbsp; In SQL Server 2005, the &lt;b&gt;sys.dm_os_performance_counters&lt;/b&gt; Dynamic Management View (DMV) is introduced to allow you to query SQL Server related performance counters directly through the database engine.&amp;nbsp; This makes is very useful when you need to quickly determine your buffer cache hit ratio or how many lock deprecated features being ran in your SQL engine.&amp;nbsp; This DMV is straightforward to use, which allows you to easily setup your own custom procedures to quickly review any performance metric that may be important to you.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;Lets run a quick query against the sys.dm_os_performance_counters DMV to see what kind of data we are dealing with:&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;select * &lt;br /&gt;from sys.dm_os_performance_counters&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Here is a simple output from my system:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.SQLPerformanceCounters/6366.1_2D00_ShowAll.JPG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.SQLPerformanceCounters/6366.1_2D00_ShowAll.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Its all SQL Server related infromation, which may not be 100% what you might want to monitor...but it is a good start.&amp;nbsp; It allows me to monitor just about any SQL Server related metric that I want.&amp;nbsp; For instance, if I wanted to see how many temp tables were currently on my system, I could write a little script like the one below.&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;select * &lt;br /&gt;from sys.dm_os_performance_counters&lt;br /&gt;where counter_name = &amp;#39;Active Temp Tables&amp;#39;&lt;br /&gt;GO&lt;br /&gt;IF OBJECT_ID(&amp;#39;tempdb..#NewTempTable&amp;#39;) IS NOT NULL&lt;br /&gt;DROP TABLE #NewTempTable&lt;br /&gt;GO&lt;br /&gt;CREATE TABLE #NewTempTable&lt;br /&gt;(&lt;br /&gt;    Field1 INT&lt;br /&gt;)&lt;br /&gt;GO&lt;br /&gt;select * &lt;br /&gt;from sys.dm_os_performance_counters&lt;br /&gt;where counter_name = &amp;#39;Active Temp Tables&amp;#39;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the output from my home system:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.SQLPerformanceCounters/3652.2_2D00_TempTables.bmp"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.SQLPerformanceCounters/3652.2_2D00_TempTables.bmp" border="0" width="575" height="460" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can see from the output above that the DMV pulls accurate information.&amp;nbsp; When I first ran the query there were no temp tables on my system.&amp;nbsp; I created a temp table, and then reran the query again.&amp;nbsp; The DMV returned that there was one active temp table.&amp;nbsp; Neat stuff...and very handy.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;While having the ability to see how many temp tables on my system might not be really useful right now (it very well could be depending on your environment), you can easily see how you could write your own custom routines to pull data from this table that is important to you.&amp;nbsp; The trickiest part is determine which counter is what you want to look at.&amp;nbsp; That is a whole other topic in and of itself.&lt;/p&gt;
&lt;p&gt;Hope this helps!&lt;br /&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=297" width="1" height="1"&gt;</content><author><name>TimChapman</name><uri>http://sqlservernation.com/members/TimChapman/default.aspx</uri></author><category term="performance OS SQL Server Tim Chapman perfmon metric" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/performance+OS+SQL+Server+Tim+Chapman+perfmon+metric/default.aspx" /></entry><entry><title>Using APPLY to match a table valued function to a table</title><link rel="alternate" type="text/html" href="/blogs/tipweek/archive/2009/09/21/using-cross-apply-to-match-a-table-valued-function-to-a-table.aspx" /><id>/blogs/tipweek/archive/2009/09/21/using-cross-apply-to-match-a-table-valued-function-to-a-table.aspx</id><published>2009-09-22T02:46:00Z</published><updated>2009-09-22T02:46:00Z</updated><content type="html">&lt;p&gt;In this article, I will explain how to use the APPLY operator to join the results of a table valued function to a table.&amp;nbsp; The APPLY operator allows a table column to be supplied as an input parameter to a table valued function.&lt;/p&gt;
&lt;p&gt;Microsoft describes the APPLY operator as &amp;quot;The new APPLY relational operator allows you to invoke a specified
table-valued function once per each row of an outer table expression&amp;quot; (&lt;a target="_self" title="APPLY" href="http://technet.microsoft.com/en-us/library/cc966521.aspx"&gt;source&lt;/a&gt;).&amp;nbsp; Basically what that means is that you can join a table valued function to another table.&amp;nbsp; Except you don&amp;#39;t use the JOIN operator.&amp;nbsp; You have to use either the CROSS APPLY or the OUTER APPLY operators.&lt;/p&gt;
&lt;p&gt;Under certain circumstances you can use the simple join syntax.&amp;nbsp; Specifically, when the function has no parameters or the parameters are variables or literals.&amp;nbsp; But when the input parameters for the table valued function come from a table, attempting to do so will generate an error.&lt;/p&gt;
&lt;p&gt;&lt;span style="color:red;"&gt;Msg 4104, Level 16, State 1, Line 2&lt;br /&gt;The multi-part identifier &amp;quot;xxxxxxxxx&amp;quot; could not be bound.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In order to demonstrate use of the APPLY syntax, we will create a sample table and load it with some sample data.&amp;nbsp; We will use the fn_DelimitedToTable function as demonstrated in the article titled &lt;a target="_blank" href="http://sqlservernation.com/blogs/howtos/archive/2009/03/07/converting-a-delimited-string-into-a-table.aspx"&gt;Converting a delimited string into a table&lt;/a&gt;.&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;create table [dbo].[test_data_1]&lt;br /&gt;     (ident         int identity primary key clustered&lt;br /&gt;     ,username      varchar(100) not null&lt;br /&gt;     ,websites      varchar(100) null&lt;br /&gt;     )&lt;br /&gt;GO&lt;br /&gt;insert into [dbo].[test_data_1](username,websites) &lt;br /&gt;  values(&amp;#39;Galderisi,Brandon&amp;#39;, &amp;#39;SQLServerNation.com,experts-exchange.com&amp;#39;)&lt;br /&gt;insert into [dbo].[test_data_1](username,websites) &lt;br /&gt;  values(&amp;#39;Chapman,Tim&amp;#39;, &amp;#39;SQLServerNation.com,experts-exchange.com&amp;#39;)&lt;br /&gt;insert into [dbo].[test_data_1](username,websites) &lt;br /&gt;  values(&amp;#39;Wills,Mark&amp;#39;, &amp;#39;SQLServerNation.com,experts-exchange.com&amp;#39;)&lt;br /&gt;insert into [dbo].[test_data_1](username,websites) &lt;br /&gt;  values(&amp;#39;Smith,Joe&amp;#39;, NULL)&lt;br /&gt;GO&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;There are two variations of the APPLY operator.&amp;nbsp; CROSS and OUTER.&amp;nbsp; The CROSS APPLY operates just like an INNER&amp;nbsp; JOIN.&amp;nbsp; It will only return values from the table that yield results from the function.&amp;nbsp; The OUTER APPLY operator, as you probably guessed, is like an OUTER JOIN.&amp;nbsp; That means that all records from the table will be returned regardless of whether or not function returns data.&lt;/p&gt;
&lt;p&gt;Below is the syntax for each, and we will then review the results.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;select t1.*,f1.theValue from [dbo].[test_data_1] t1&lt;br /&gt;  cross apply [dbo].[fn_DelimitedToTable](t1.websites,&amp;#39;,&amp;#39;) f1&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.09.22/5100.res1.PNG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.09.22/5100.res1.PNG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;select t1.*,f1.theValue from [dbo].[test_data_1] t1&lt;br /&gt;  outer apply [dbo].[fn_DelimitedToTable](t1.websites,&amp;#39;,&amp;#39;) f1&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.09.22/8750.res2.PNG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.09.22/8750.res2.PNG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;The results show that each record returned from the function, yields a new row.&amp;nbsp; As stated previously the CROSS APPLY operator will operate like and INNER JOIN.&amp;nbsp; Therefore, the record for Jim Smith is omitted from the first result set because the value used as the input parameter is NULL and the function we are using only returns data if data is input.&lt;/p&gt;
&lt;p&gt;I hope that you have found this useful.&amp;nbsp; As always, if you have any questions about how this can apply to your situation, stop by our forums.&amp;nbsp; If you would prefer you may also email me at BrandonGalderisi {AT} our website dot com.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;create table [dbo].[test_data_1]&lt;br /&gt;     (ident         int identity primary key clustered&lt;br /&gt;     ,delimited     varchar(100)&lt;br /&gt;     )&lt;br /&gt;GO&lt;br /&gt;insert into [dbo].[test_data_1](delimited) values(&amp;#39;Galderisi,Brandon&amp;#39;)&lt;br /&gt;insert into [dbo].[test_data_1](delimited) values(&amp;#39;Chapman,Tim&amp;#39;)&lt;br /&gt;insert into [dbo].[test_data_1](delimited) values(&amp;#39;Wills,Mark&amp;#39;)&lt;br /&gt;GO&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&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=287" width="1" height="1"&gt;</content><author><name>BrandonGalderisi</name><uri>http://sqlservernation.com/members/BrandonGalderisi/default.aspx</uri></author><category term="Intermediate SQL" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Intermediate+SQL/default.aspx" /><category term="User Defined Functions" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/User+Defined+Functions/default.aspx" /><category term="JOIN" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/JOIN/default.aspx" /><category term="APPLY" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/APPLY/default.aspx" /></entry><entry><title>Relating SQL Server profiler with Performance monitor</title><link rel="alternate" type="text/html" href="/blogs/tipweek/archive/2009/07/11/relating-sql-server-profiler-with-performance-monitor.aspx" /><id>/blogs/tipweek/archive/2009/07/11/relating-sql-server-profiler-with-performance-monitor.aspx</id><published>2009-07-11T16:00:00Z</published><updated>2009-07-11T16:00:00Z</updated><content type="html">&lt;p&gt;A DBA would often experience the need to explore performance
bottlenecks, to create benchmarking reports and to overview the SQL Server
performance as part of its daily existence. Any of the above (including the
daily existence) can easily become a daunting task if the &amp;quot;tools of the trade&amp;quot;
are not known and mastered. Hence, in this short article we will concentrate
our attention on a very rewarding aspect of the Windows Server and SQL Server
performance monitoring tools.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;In this article I will not go in detail about how to start
perfmon and Profiler, nor how to save the output to file. I believe this is a
subject of a completely different article. What we would see here is how to
build a bridge over the river of information flowing between both tools. &lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Here is what you will need for the purpose:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A perfmon generated file,
     in either *.csv or *.blg format&lt;/li&gt;
&lt;li&gt;A Profiler trace, saved to
     disk as a *.trc file.&lt;/li&gt;
&lt;li&gt;Plenty of good intention
     and sophistication when it comes to the gathered set of perfmon counters
     and Profiler events. Keep in mind that you have to know what you are looking
     for and get to it in the most efficient and modest way possible. &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Let&amp;#39;s suppose that we already have saved both files on disk
and that we are ready to tackle the problem we are after.
&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;1. Start SQL Server Profiler,
     and open the saved *.trc file.&lt;/p&gt;
&lt;ol&gt;
&lt;/ol&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/8664.Profiler1.jpg"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/8664.Profiler1.jpg" border="0" alt="" /&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;2. Click on &amp;quot;File &amp;gt; Import
     Performance Data... &amp;quot; and you will be presented with a file browsing window
     like this one.&lt;/p&gt;
&lt;ol&gt;
&lt;/ol&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/5432.WindowsExplorer1.jpg"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/5432.WindowsExplorer1.jpg" border="0" alt="" /&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;3. Point to the perfmon file
     (csv or blg) and click Open. Of course, the trace file and the perfmon
     file must have something in common. I.e. a trace which was stopped last
     month will not correlate with a perfmon file started today. &lt;/p&gt;
&lt;ol&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;4. After you point to the
     perfmon file and click &amp;quot;Open&amp;quot;, you will be presented with the following
     &amp;quot;Performance Counters Limit Dialog&amp;quot;. As you can see, here you can select
     the performance counters which you are interested in and profiler will
     import them in correlation with the currently open trace. In an ideal
     situation you will select a few counters, thus making it easy to read and analyze.
     &lt;/p&gt;
&lt;ol&gt;
&lt;/ol&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/8750.PerformanceCountersList.jpg"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/8750.PerformanceCountersList.jpg" border="0" alt="" /&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;5. After selecting the
     desired counters by checking the appropriate checkboxes, click OK, and viola!
     You will be presented with a colorful graphic of the perfmon counters&amp;#39; ups
     and downs within the Profiler. &lt;/p&gt;
&lt;ol&gt;
&lt;/ol&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/5025.Profiler_5F00_Perfmon1.jpg"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/5025.Profiler_5F00_Perfmon1.jpg" border="0" alt="" /&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;6. Let&amp;#39;s submerge us in a bit
     more detail: in the profiler now you see 4 different layers of
     information: trace events on top, the color diagram, the perfmon counter
     list and the actual Profiler&amp;#39;s TextData field displayed on the bottom.
&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;7. You have several options
     for exploring the information.
&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;a. You can click on any line
      of the trace event and see the corresponding values of the perfmon
      counters&lt;/li&gt;
&lt;li&gt;You can click on the
      perfmon&amp;#39;s graphics and you will be taken to the profiler event which
      happened at that time.&amp;nbsp; &lt;/li&gt;
&lt;li&gt;You can right-click on
      one of the counters in the list and you can select &amp;quot;Go to max value&amp;quot; or
      &amp;quot;Go to min value&amp;quot;&lt;/li&gt;
&lt;li&gt;You can review the t-sql
      in the bottom window&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p style="padding-left:30px;"&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/5047.Profiler_5F00_Perfmon2.jpg"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/5047.Profiler_5F00_Perfmon2.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style="padding-left:30px;"&gt;8. Don&amp;#39;t forget that the
     world IS what you make out of it! It is up to your imagination what set of
     counters you will gather as well as what events and columns. Just make
     sure you use a server side trace and keep in mind that there is a way to
     gather perfmon counters remotely as well. (This is a topic for another
     article.)Think about the performance of your own performance measurements.
     Do not bring the system down, just because you wanted to know what is
     already bringing the system down. &lt;/p&gt;
&lt;ol&gt;
&lt;/ol&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=265" width="1" height="1"&gt;</content><author><name>sibir1us</name><uri>http://sqlservernation.com/members/sibir1us/default.aspx</uri></author></entry><entry><title>Introduction to SQL Server Profiler</title><link rel="alternate" type="text/html" href="/blogs/tipweek/archive/2009/05/27/introduction-to-sql-server-profiler.aspx" /><id>/blogs/tipweek/archive/2009/05/27/introduction-to-sql-server-profiler.aspx</id><published>2009-05-28T00:12:00Z</published><updated>2009-05-28T00:12:00Z</updated><content type="html">&lt;p&gt;How often do you find yourself with the need to know what statements are occurring on your SQL Server?&amp;nbsp; Well, if you&amp;#39;re like me the answer is &amp;quot;ALL THE TIME&amp;quot;.&amp;nbsp; Constantly Im asked about processes running on the server if things are slow.&amp;nbsp; Even more importantly, I like to capture data from the system to use for trending purposes and to proactively diagnose performance problems before someone comes and tells me about it.&amp;nbsp; Luckily, SQL Server comes with tools to capture such data, and to save it for later use.&amp;nbsp; The tool is SQL Server Profiler, and it is wonderful for setting up trace files to save data so that you can report on the happenings on the server.&lt;/p&gt;
&lt;p&gt;To start a new SQL Server Profiler session, you can click on the Tools menu and select it from the downdown (so long as you&amp;#39;re not using SQL Server Express edition - Profiler does not come with Express).&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/1.JPG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/1.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Selecting Profiler from the menu brings up the SQL Server Profiler application.&amp;nbsp; At this point, you&amp;#39;ll want to create a New Trace.&amp;nbsp; This can be done from the File menu, or by pressing Control + N.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/2.JPG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/2.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;A Bit about Traces&lt;br /&gt;&lt;/b&gt;A trace is a SQL Server object that
captures information regarding certain events that occur on the
database instance.&amp;nbsp; The information traces gather can be as simple as
catching stored procedure executions to as complex as catching deadlock
graphs.&amp;nbsp; Whats great about them is that you can select certain columns
of data as well as filter for certain events.&amp;nbsp; These traces are
absolutely imperative for any SQL Server DBA to use, and are extremely
handy for diagnosing problems on the server, such as performance
problems.&amp;nbsp; SQL Server Profiler is a graphical user interface (GUI) for
creating a grouping of traces.&amp;nbsp; It makes it super easy to define them,
alter them, and save them.&amp;nbsp; However, if you want to define your own
custom trace files through TSQL, I show you how &lt;a title="Define Custom Trace Files" href="http://blogs.techrepublic.com.com/datacenter/?p=159"&gt;in this article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To run a new trace, you&amp;#39;ll first need to connect to an instance of SQL Server.&amp;nbsp; If you&amp;#39;re using 2005 or 2008 a screen similar to the one below should appear.&amp;nbsp; You can type your instance name (your server name if it is the default instance) or select your instance from the dropdown box.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/3.JPG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/3.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Once I am connected to my server, I can define the Events, Columns and Filters I want to use in my trace.&amp;nbsp; SQL Profiler comes with a handy set of predefined trace templates that you can use to capture common data.&amp;nbsp; For the most part, these are great if you have a limited set of needs for your trace file. However, if you want more specific information you&amp;#39;re going to need to define your own trace or alter some of the predefined templates.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;In the trace properties screen I have the option to save the trace data to a file or to a table, or both.&amp;nbsp; If you want to save the data you capture, here is a good place to do it.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/4.JPG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/4.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Clicking on the Events Selection tab I can set filters for my trace events.&amp;nbsp; For example, in the screenshot below I am setting criteria to only capture events where the event is intitiated by the LoginName &amp;quot;thedude&amp;quot;.&amp;nbsp; More realistic filters may include looking for logical Reads &amp;gt; 1000 or a duration of a query longer than 2 seconds.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/5.JPG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/5.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;In the same screen I can decide which columns of data are important to me.&amp;nbsp; If I am capturing information regarding statements running, stored proc execution, etc then I want to know such informaiton as the name of the procedure, statement running, duration, reads, writes, etc.&amp;nbsp; I can use this information to find those procedures which may be resource hogs.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/6.JPG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/6.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;
I can also easily organize the order in which the columns appear.&amp;nbsp; I can also group them, which I appreciate doing after I&amp;#39;ve captured the trace as it allows me to group the durations and reads of statements to easily identify longer running statements.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/8.JPG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/8.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Below is a sample output of what statements were captured on my system while I was running the trace file.&amp;nbsp; Notice the amount of detail that is showed through the trace.&amp;nbsp; I am easily able to capture the Reads, CPU, SQL Statement, and Duration of the statement just through the use of the trace file.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/9.JPG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/9.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Once you have your trace defined the way you want it, you have a number of options as to how you want to save it.&amp;nbsp; You can save the results from the trace to a File (flat or XML) or a Table.&amp;nbsp; I like to use the Table option since it allows me to view the data in a format to which I am accustomed to using, and so I can easily query it how I want.&amp;nbsp; What is also great about the Save option is that I can save the definition of the trace file so that I can use it later.&amp;nbsp; From there I can view the TSQL which comprises the trace so that I can save it later.&amp;nbsp; I use this method when I want to quickly define a trace, and then create a stored procedure that runs on system start up to start the trace.&amp;nbsp; Doing so make sure I can automatically capture events without having to go through the manual interaction to do so.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/10.JPG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/10.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Conclusion&lt;/b&gt;&lt;br /&gt;Today I took a broad overview of what SQL Server Profiler is, what it does, and what you can use it for.&amp;nbsp; I hope it is a great place to get started opening the window to the world on your SQL Server.&amp;nbsp; However, care must be taken when using the tool.&amp;nbsp; If you try to capture too many events then you&amp;#39;re risking slowing down your server because there is overhead in capturing the events.&amp;nbsp; In a later writing, I plan to dig a little more deeply into these issues, and how you can use Profiler to diagnose some common performance problems on your server.&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=14" width="1" height="1"&gt;</content><author><name>TimChapman</name><uri>http://sqlservernation.com/members/TimChapman/default.aspx</uri></author><category term="Profiler" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Profiler/default.aspx" /><category term="Tim Chapman" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Tim+Chapman/default.aspx" /><category term="Trace" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Trace/default.aspx" /><category term="Table" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Table/default.aspx" /><category term="SQL Server" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/SQL+Server/default.aspx" /></entry><entry><title>When an outer join becomes an inner join</title><link rel="alternate" type="text/html" href="/blogs/tipweek/archive/2009/05/20/when-an-outer-join-becomes-an-inner-join.aspx" /><id>/blogs/tipweek/archive/2009/05/20/when-an-outer-join-becomes-an-inner-join.aspx</id><published>2009-05-21T01:00:00Z</published><updated>2009-05-21T01:00:00Z</updated><content type="html">&lt;p&gt;Enclosed you will find how an improperly constructed OUTER JOIN query can turn into an INNER JOIN.&lt;/p&gt;
&lt;p&gt;An OUTER JOIN is described as a join where you want all data from one table along with any matching data from another table.&amp;nbsp; In SQL Server the default&lt;code&gt; behavior of using JOIN creates an INNER JOIN.&amp;nbsp; But sometimes even if you use LEFT JOIN or LEFT OUTER JOIN, the query that you build may end up being an INNER JOIN.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The first thing we will do is setup some test tables.&amp;nbsp; Before you critique the tables, realize that this is simple test data and is not what we would build for an employee employer table.&amp;nbsp; The data sample below will add three employees.&amp;nbsp; Two of those employees will be associated with employers in the #Employers table.&amp;nbsp; Both of those employers will have a simple address.&lt;/p&gt;
&lt;p&gt;Temporary tables:&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;set nocount on&lt;br /&gt;go&lt;br /&gt;create table #Employees&lt;br /&gt;     (EmployeeId    int primary key clustered&lt;br /&gt;     ,EmployerId    int null&lt;br /&gt;     ,FirstName     varchar(20) not null&lt;br /&gt;     ,LastName      varchar(20) not null&lt;br /&gt;     )&lt;br /&gt;create table #Employers&lt;br /&gt;     (EmployerId    int primary key clustered&lt;br /&gt;     ,EmployerName  varchar(30) not null&lt;br /&gt;     )&lt;br /&gt;create table #EmployerAddress&lt;br /&gt;     (EmployerId    int primary key clustered&lt;br /&gt;     ,EmployerCity  varchar(30) not null&lt;br /&gt;     ,EmployerState varchar(3) not null&lt;br /&gt;     )&lt;br /&gt;go&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Test data:&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;insert into #Employees(EmployeeId,EmployerId,FirstName,LastName)values(1,1,&amp;#39;Brandon&amp;#39;,&amp;#39;Galderisi&amp;#39;)&lt;br /&gt;insert into #Employees(EmployeeId,EmployerId,FirstName,LastName)values(2,2,&amp;#39;Tim&amp;#39;,&amp;#39;Chapman&amp;#39;)&lt;br /&gt;insert into #Employees(EmployeeId,EmployerId,FirstName,LastName)values(3,null,&amp;#39;Mike&amp;#39;,&amp;#39;Jones&amp;#39;)&lt;br /&gt;&lt;br /&gt;insert into #Employers(EmployerId,EmployerName)values(1,&amp;#39;Company A&amp;#39;)&lt;br /&gt;insert into #Employers(EmployerId,EmployerName)values(2,&amp;#39;Company B&amp;#39;)&lt;br /&gt;&lt;br /&gt;insert into #EmployerAddress(EmployerId,EmployerCity,EmployerState)values(1,&amp;#39;Anytown&amp;#39;,&amp;#39;USA&amp;#39;)&lt;br /&gt;insert into #EmployerAddress(EmployerId,EmployerCity,EmployerState)values(2,&amp;#39;Washington&amp;#39;,&amp;#39;DC&amp;#39;)&lt;br /&gt;go&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Now let&amp;#39;s look at the first simple query.&amp;nbsp; This query simply selects all #Employees with the #Employer records that are available.&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;select * from #Employees ee&lt;br /&gt;  left outer join #Employers er&lt;br /&gt;    on ee.EmployerId = er.EmployerId&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.05.21.When_5F00_INNER_5F00_becomes_5F00_OUTER/2146.res1.png"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.05.21.When_5F00_INNER_5F00_becomes_5F00_OUTER/2146.res1.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Now let&amp;#39;s add in the #Employer_Address table.&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;select * from #Employees ee&lt;br /&gt;  left outer join #Employers er&lt;br /&gt;    on ee.EmployerId = er.EmployerId&lt;br /&gt;  left outer join #EmployerAddress era&lt;br /&gt;    on er.EmployerId = era.EmployerId&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.05.21.When_5F00_INNER_5F00_becomes_5F00_OUTER/2260.res2.PNG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.05.21.When_5F00_INNER_5F00_becomes_5F00_OUTER/2260.res2.PNG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;So far so good.&amp;nbsp; The LEFT OUTER JOINs are working correctly still displaying all of the employees regardless of them having a listed employer.&amp;nbsp; Now let&amp;#39;s change it.&amp;nbsp; Suppose you want to show all employees, but the employer only if they are in Washington.&amp;nbsp; So how would you do it?&amp;nbsp; Your first instinct may be to add criteria EmployerCity = &amp;#39;Washington&amp;#39;.&amp;nbsp; Let&amp;#39;s see what that does.&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;select * from #Employees ee&lt;br /&gt;  left outer join #Employers er&lt;br /&gt;    on ee.EmployerId = er.EmployerId&lt;br /&gt;  left outer join #EmployerAddress era&lt;br /&gt;    on er.EmployerId = era.EmployerId&lt;br /&gt;where era.EmployerCity = &amp;#39;Washington&amp;#39;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What you will see that not only did it remove the Employer information, but it filtered down to only employees who work for that employer.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.05.21.When_5F00_INNER_5F00_becomes_5F00_OUTER/5280.res3.PNG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.05.21.When_5F00_INNER_5F00_becomes_5F00_OUTER/5280.res3.PNG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;One way to solve this is to create a DERIVED table and JOIN to it.&amp;nbsp; Since we only want employers who are in Washington, the DERIVED table will have an inner join on #Employers and #EmployerAddress.&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;select * from #Employees ee&lt;br /&gt;  left outer join &lt;br /&gt;     (select er.EmployerId,er.EmployerName,era.EmployerCity,era.EmployerState&lt;br /&gt;      from #Employers er&lt;br /&gt;       INNER join #EmployerAddress era&lt;br /&gt;         on er.EmployerId = era.EmployerId&lt;br /&gt;      where era.EmployerCity = &amp;#39;Washington&amp;#39;&lt;br /&gt;     ) er&lt;br /&gt;    on ee.EmployerId = er.EmployerId&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;What you will see is that now we have all 3 employees.&amp;nbsp; But we only list the employer information for those in Washington.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.05.21.When_5F00_INNER_5F00_becomes_5F00_OUTER/4812.res4.PNG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.05.21.When_5F00_INNER_5F00_becomes_5F00_OUTER/4812.res4.PNG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;I hope that you found this post useful and that it brings light to the fact that improperly constructed JOIN statements can yield undesired results.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://sqlservernation.com/aggbug.aspx?PostID=189" width="1" height="1"&gt;</content><author><name>BrandonGalderisi</name><uri>http://sqlservernation.com/members/BrandonGalderisi/default.aspx</uri></author><category term="Intermediate SQL" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Intermediate+SQL/default.aspx" /><category term="JOIN" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/JOIN/default.aspx" /></entry><entry><title>The Numbers Table (Round 2)</title><link rel="alternate" type="text/html" href="/blogs/tipweek/archive/2009/04/27/the-numbers-table-round-2.aspx" /><id>/blogs/tipweek/archive/2009/04/27/the-numbers-table-round-2.aspx</id><published>2009-04-28T01:29:00Z</published><updated>2009-04-28T01:29:00Z</updated><content type="html">&lt;p&gt;Originally I wrote about The Numbers Table over at &lt;a title="The Numbers Table" href="http://www.sqlservercentral.com/articles/T-SQL+Aids/64696/"&gt;SQL Server Central&lt;/a&gt;.&amp;nbsp; Over there I presented the structure and a couple options on how to populate it.&amp;nbsp; I won&amp;#39;t dwell on all of that again.&amp;nbsp; I&amp;#39;m just going to keep it simple.&lt;/p&gt;
&lt;h3&gt;What is a numbers table?&lt;/h3&gt;
&lt;p&gt;A numbers table is a table containing sequential integer values. The
size of your table will vary depending upon how you may choose to use
it. Most practical applications that I have encountered have never
called for more than 100,000 records but we will be creating ours with
1,000,000 records just to be safe. Remember that the larger you choose
to create your numbers table, the more space it will take up and the
longer it will take to generate.&lt;/p&gt;
&lt;h3&gt;Where to put the table?&lt;br /&gt;&lt;/h3&gt;
&lt;p&gt;An argument can be made that that this table belongs in your user database.&amp;nbsp; But if you have 10 user databases on your server, do you really need to numbers tables?&amp;nbsp; That would also mean that every full database backup will have the extra space in it.&amp;nbsp; This could quickly add up to a lot of wasted space both in your databases and in your backups.&amp;nbsp; That&amp;#39;s why I suggest keeping a single utility database per server.&amp;nbsp; This single database can be used to store all of your utility functions, tables and procedures.&lt;/p&gt;
&lt;h3&gt;The Structure&lt;/h3&gt;
&lt;p&gt;The structure of a numbers table is very simple. It must only
contain one column and it is the clustered index. For
performance reasons, the table is created, populated, then the
clustered index is applied.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;create table util_nums&lt;br /&gt;     (n     int not null&lt;br /&gt;     ,constraint pk_util_nums primary key clustered(n) with fillfactor = 100&lt;br /&gt;     )&lt;/code&gt;&lt;code&gt;&lt;br /&gt;go&lt;br /&gt;alter table util_nums add constraint &lt;br /&gt;   pk_util_nums primary key clustered(n)with fillfactor =100&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;For populating this table, again I&amp;#39;m not going to dwell on the different ways to do it.&amp;nbsp; So I will use the nested CTE method.&amp;nbsp; It gets the job done, and it&amp;#39;s fast enough.&amp;nbsp; This method uses a series of cross joins in common table expressions to generate rows.&amp;nbsp; The rows are then numbered using the row_number() function.&amp;nbsp; This is the same SQL used in the &lt;a title="A View of Numbers" href="http://sqlservernation.com/blogs/howtos/archive/2009/03/07/a-view-of-nmbers.aspx"&gt;A View of Numbers&lt;/a&gt; article, except it inserts it into the util_nums table.&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;;with   cte0 as (select 1 as c union all select 1),&lt;br /&gt;       cte1 as (select 1 as c from cte0 a, cte0 b),&lt;br /&gt;       cte2 as (select 1 as c from cte1 a, cte1 b),&lt;br /&gt;       cte3 as (select 1 as c from cte2 a, cte2 b),&lt;br /&gt;       cte4 as (select 1 as c from cte3 a, cte3 b),&lt;br /&gt;       cte5 as (select 1 as c from cte4 a, cte4 b),&lt;br /&gt;       nums as (select row_number() over (order by c) as n from cte5)&lt;br /&gt; insert into util_nums(n)&lt;br /&gt;       select n from nums&lt;br /&gt;       where n &amp;lt;= 1000000&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;A numbers table can have so many functions.&amp;nbsp; Both the &lt;a title="Chars to Table" href="http://sqlservernation.com/blogs/howtos/archive/2009/03/24/splitting-a-string-into-a-table-of-characters.aspx"&gt;fn_CharsToTable&lt;/a&gt; and the &lt;a title="HASHBYTES MAX" href="http://sqlservernation.com/blogs/howtos/archive/2009/03/15/hashing-large-data-strings.aspx"&gt;fn_HashBytesMAX&lt;/a&gt; function can be updated to use the util_nums table instead of the vw_nums view.&amp;nbsp; And in the future, all functions that I post on here will assume that you have the numbers table, instead of the &lt;a title="A View of Numbers" href="http://sqlservernation.com/blogs/howtos/archive/2009/03/07/a-view-of-nmbers.aspx"&gt;vw_nums&lt;/a&gt; view.&lt;/p&gt;
&lt;h3&gt;&lt;/h3&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://sqlservernation.com/aggbug.aspx?PostID=181" width="1" height="1"&gt;</content><author><name>BrandonGalderisi</name><uri>http://sqlservernation.com/members/BrandonGalderisi/default.aspx</uri></author><category term="views" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/views/default.aspx" /><category term="Intermediate SQL" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Intermediate+SQL/default.aspx" /><category term="Beginner SQL" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Beginner+SQL/default.aspx" /><category term="Advanced SQL" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Advanced+SQL/default.aspx" /><category term="User Defined Functions" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/User+Defined+Functions/default.aspx" /></entry><entry><title>Fast searching of large (n)varchar values</title><link rel="alternate" type="text/html" href="/blogs/tipweek/archive/2009/04/02/Fast-searching-of-large-_2800_n_2900_varchar-values.aspx" /><id>/blogs/tipweek/archive/2009/04/02/Fast-searching-of-large-_2800_n_2900_varchar-values.aspx</id><published>2009-04-02T05:00:00Z</published><updated>2009-04-02T05:00:00Z</updated><content type="html">&lt;p&gt;In this Tip of the Week post, we will discuss how you can index a large data field to allow fast equality searching. What does this mean?&amp;nbsp; It means that you won&amp;#39;t be able to do indexed LIKE searches, but you will be able to do indexed = searches.&lt;/p&gt;
&lt;p&gt;Why is this necessary?&amp;nbsp; This is necessary due to the size limitation of indexes in SQL Server.&amp;nbsp; SQL Server does not allow you to create an index on any field, or combination of fields, whose length is greater than 900 bytes. For varchar fields, each character is a byte.&amp;nbsp; For nvarchar fields, each character is two bytes.&amp;nbsp; That means that you cannot create an index on a varchar field that is greater than 900 characters, or a nvarchar field that is greateer than 450 characters.&amp;nbsp; This can prove to be quite limiting.&amp;nbsp; And performing a search on these fields will result in a clustered index scan.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;The below trick uses a function previously covered in the topic &lt;a title="Hashing large data strings with a User Defined Function" href="http://sqlservernation.com/blogs/howtos/archive/2009/03/15/hashing-large-data-strings.aspx"&gt;Hashing large data strings with a User Defined Function&lt;/a&gt; to generate a hash of the data.&amp;nbsp; The hashed column is then indexed so that searches can be done via the hash.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#99d9ea;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;Note: All of the SQL in this article may be downloaded with the link at the bottom of the page.&lt;br /&gt;      You will notice that the names look strange.  Even though we always recommend running&lt;br /&gt;      our tests in a secluded environment, these names are meant to prevent name conflicts should&lt;br /&gt;      you need to run them in an existing database.&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Now let&amp;#39;s get started by setting up all of the items necessary for this article.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;create table [util_Nums_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;     (n   int not null&lt;br /&gt;     ,constraint [pk_util_Nums_361A370A-D881-4612-BE1C-916BD466E884] &lt;br /&gt;      primary key clustered (n)&lt;br /&gt;     )&lt;br /&gt;go&lt;br /&gt;;with   cte0 as (select 1 as c union all select 1), &lt;br /&gt;       cte1 as (select 1 as c from cte0 a, cte0 b), &lt;br /&gt;       cte2 as (select 1 as c from cte1 a, cte1 b), &lt;br /&gt;       cte3 as (select 1 as c from cte2 a, cte2 b), &lt;br /&gt;       cte4 as (select 1 as c from cte3 a, cte3 b), &lt;br /&gt;       cte5 as (select 1 as c from cte4 a, cte4 b), &lt;br /&gt;       nums as (select top 1000000 row_number() &lt;br /&gt;                                   over (order by c) as n from cte5)&lt;br /&gt;insert into [util_Nums_361A370A-D881-4612-BE1C-916BD466E884](n)&lt;br /&gt;select n from nums &lt;br /&gt;go&lt;br /&gt;create function dbo.[fn_SplitEveryX_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;    (@InputString nvarchar(max)&lt;br /&gt;    ,@Every int&lt;br /&gt;    )&lt;br /&gt;returns @Data table &lt;br /&gt;     (ident    int identity&lt;br /&gt;     ,theData  nvarchar(max)&lt;br /&gt;     )&lt;br /&gt;as&lt;br /&gt;/************************************************************&lt;br /&gt;*&lt;br /&gt;*    Author:        Brandon Galderisi&lt;br /&gt;*    Last modified: 01-April-2009&lt;br /&gt;*    Purpose:       Splits an @InputString every @Every &lt;br /&gt;                    characters returning a table.&lt;br /&gt;*&lt;br /&gt;*************************************************************/&lt;br /&gt;&lt;br /&gt;begin&lt;br /&gt;     insert into @Data (theData)&lt;br /&gt;    select substring(@inputstring, n , @every)&lt;br /&gt;    from    dbo.[util_Nums_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;    where n &amp;lt;= (datalength(@Inputstring)/2)&lt;br /&gt;        and (n-1)%@every=0&lt;br /&gt;return &lt;br /&gt;end&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;create function dbo.[fn_hashbytesMAX_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;     (@string nvarchar(max)&lt;br /&gt;     ,@Algo    varchar(10)&lt;br /&gt;    )&lt;br /&gt;    returns binary(16)&lt;br /&gt;--WITH ENCRYPTION&lt;br /&gt;as&lt;br /&gt;/************************************************************&lt;br /&gt;*&lt;br /&gt;*    Author:        Brandon Galderisi&lt;br /&gt;*    Last modified: 01-April-2009&lt;br /&gt;*    Purpose:       uses the system function hashbytes as well&lt;br /&gt;*                   as sys.fn_varbintohexstr to split an &lt;br /&gt;*                   nvarchar(max) string and hash in 8000 byte &lt;br /&gt;*                   chunks hashing each 8000 byte chunk,,&lt;br /&gt;*                   getting the 32 byte output, streaming each &lt;br /&gt;*                   32 byte output into a string then hashing &lt;br /&gt;*                   that string.&lt;br /&gt;*&lt;br /&gt;*************************************************************/&lt;br /&gt;begin&lt;br /&gt;     declare    @concat       nvarchar(max)&lt;br /&gt;               ,@NumHash      int&lt;br /&gt;               ,@HASH         binary(16)&lt;br /&gt;     set @NumHash = ceiling((datalength(@string)/2)/(4000.0))&lt;br /&gt;    /* HashBytes only supports 8000 bytes so split the string if it is larger */&lt;br /&gt;    if @NumHash&amp;gt;1&lt;br /&gt;    begin&lt;br /&gt;          ;with factors as &lt;br /&gt;          (select top (@NumHash) n,(n*4000)+1 factor &lt;br /&gt;           from [util_Nums_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;          )&lt;br /&gt;         select @concat = &lt;br /&gt;               cast(&lt;br /&gt;               (select right(sys.fn_varbintohexstr&lt;br /&gt;                               (&lt;br /&gt;                                hashbytes(@Algo, &lt;br /&gt;                                          substring(@string, factor - 4000, 4000)&lt;br /&gt;                                          )&lt;br /&gt;                               )&lt;br /&gt;                            ,32)+&amp;#39;&amp;#39;&lt;br /&gt;              from Factors&lt;br /&gt;               for xml path(&amp;#39;&amp;#39;)&lt;br /&gt;               ) as varchar(max))&lt;br /&gt;&lt;br /&gt;         set @HASH = dbo.[fn_hashbytesMAX_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;                                         (@concat,@Algo)&lt;br /&gt;    end&lt;br /&gt;     else&lt;br /&gt;     begin&lt;br /&gt;          set @HASH = convert(binary(16), hashbytes(@Algo, @string))&lt;br /&gt;     end&lt;br /&gt;&lt;br /&gt;return @HASH&lt;br /&gt;end&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;create table [IndexedData_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;     (id       int identity not null primary key clustered&lt;br /&gt;     ,theData  varchar(1000) not null&lt;br /&gt;     ,theHash  binary(16) not null&lt;br /&gt;     )&lt;br /&gt;go&lt;br /&gt;declare @TestData nvarchar(max) &lt;br /&gt;&lt;br /&gt;select @TestData = cast((&lt;br /&gt;select char(n%255) from &lt;br /&gt;(select top 1000000 n from [util_Nums_361A370A-D881-4612-BE1C-916BD466E884] ) nums&lt;br /&gt;for xml path(&amp;#39;&amp;#39;)&lt;br /&gt;) as nvarchar(max))&lt;br /&gt;insert into [IndexedData_361A370A-D881-4612-BE1C-916BD466E884](theData,theHash)&lt;br /&gt;select theData, dbo.[fn_hashbytesMAX_361A370A-D881-4612-BE1C-916BD466E884](theData,&amp;#39;MD5&amp;#39;)&lt;br /&gt;from dbo.[fn_SplitEveryX_361A370A-D881-4612-BE1C-916BD466E884](@TestData,901)&lt;br /&gt;&lt;br /&gt;go&lt;br /&gt;create index [idx_IndexedData_361A370A-D881-4612-BE1C-916BD466E884_theHASH]&lt;br /&gt;  on [IndexedData_361A370A-D881-4612-BE1C-916BD466E884] (theHash)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;The above code will load 1110 records into the [IndexedData_361A370A-D881-4612-BE1C-916BD466E884] table.&amp;nbsp; Now we will choose a value to search for and compare the execution plans.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#99d9ea;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;To show the Execution Plan in SSMS&lt;br /&gt;Click: Query &amp;gt; Include Actual Execution Plan&lt;br /&gt;Or Press &amp;lt;CTRL&amp;gt;+M&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;declare &lt;br /&gt;      @dataToSearchFor varchar(1000)&lt;br /&gt;     ,@hashToSearchFor binary(16)&lt;br /&gt;select top 1  @DataToSearchFor = theData&lt;br /&gt;from [IndexedData_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;order by newid()&lt;br /&gt;&lt;br /&gt;set @hashToSearchFor = dbo.[fn_hashbytesMAX_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;    (@DataToSearchFor,&amp;#39;MD5&amp;#39;)&lt;br /&gt;&lt;br /&gt;select * from [IndexedData_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;where theData = @DataToSearchFor &lt;br /&gt;&lt;br /&gt;select * from [IndexedData_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;where theHash = @hashToSearchFor &lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;The first thing to note is that the two result sets returned are identical.&amp;nbsp; This is how we know that the search function returns the same data.&amp;nbsp; Now let&amp;#39;s look at the execution plans.&amp;nbsp; We will ignore the first two Queries from the execution plan because they are just used to pick a value from &amp;quot;theData&amp;quot; column and get the hash for it.&amp;nbsp; Since the goal is that you will be searching for a particular string of data, this will not factor into your production code.&lt;/p&gt;
&lt;p&gt;The Query Cost of the two queries below do not add up to 100%, that&amp;#39;s because of the cost of choosing a value.&amp;nbsp; But what you will notice is that the first query, that simply queries the table looking in the NON-INDEXED &amp;quot;theData&amp;quot; column has over 10 times the Query Cost as the second.&amp;nbsp; That is because SQL Server needs to scan the clustered index and compare every value to the value being searched for.&amp;nbsp; The cost of the second query can be further broken down into it&amp;#39;s two components.&amp;nbsp; The first thing it does is an index Seek at a cost of 35%.&amp;nbsp; This is the Seek of the index on &amp;quot;theHash&amp;quot; which retrieves the values of the clustered index.&amp;nbsp; The second part, the Clustered Index Seek, has a cost of 65%.&amp;nbsp; This is only in there because we did a select * from the table.&amp;nbsp; If we were to limit the data being selected to the primary key (as we will show you next) the execution plan will change drastically.&amp;nbsp; It is worth noting now that the costs of 35% and 65% are the cost relative to Query 4 and not the overall execution of the statement.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.04.01.Fast+searching+of+large+_2800_n_2900_varchar+values/eplan1.png"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.04.01.Fast+searching+of+large+_2800_n_2900_varchar+values/eplan1.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.04.01.Fast+searching+of+large+_2800_n_2900_varchar+values/eplan2.png"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.04.01.Fast+searching+of+large+_2800_n_2900_varchar+values/eplan2.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Next let&amp;#39;s change the select statement to only return the Id column.&amp;nbsp; The primary key.&amp;nbsp; This is a more practical usage and will show how selecting unnecessary columns effects the overall performance of a query.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;declare &lt;br /&gt;      @dataToSearchFor varchar(1000)&lt;br /&gt;     ,@hashToSearchFor binary(16)&lt;br /&gt;select top 1  @DataToSearchFor = theData&lt;br /&gt;from [IndexedData_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;order by newid()&lt;br /&gt;&lt;br /&gt;set @hashToSearchFor = dbo.[fn_hashbytesMAX_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;    (@DataToSearchFor,&amp;#39;MD5&amp;#39;)&lt;br /&gt;&lt;br /&gt;select id from [IndexedData_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;where theData = @DataToSearchFor &lt;br /&gt;&lt;br /&gt;select id from [IndexedData_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;where theHash = @hashToSearchFor &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;The first thing to verify is that your records returned are identical.&lt;/p&gt;
&lt;p&gt;Then what you will now notice is that the cost of the query has been significantly reduced for the index seek.&amp;nbsp; This is because the index contains the Id value as the primary key and therefore the Clustered Index seek that was being done previously no longer occurs.&amp;nbsp; The reason why changing the &amp;quot;select *&amp;quot; to &amp;quot;select id&amp;quot; has no positive impact on the first query is that the Clustered Index is still being scanned.&amp;nbsp; And since when the clustered index is being scanned all values are available, there is no performance increase (other than reduced I/O and network bandwidth to transfer the results) by reducing the selected columns.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.04.01.Fast+searching+of+large+_2800_n_2900_varchar+values/eplan3.png"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.04.01.Fast+searching+of+large+_2800_n_2900_varchar+values/eplan3.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.04.01.Fast+searching+of+large+_2800_n_2900_varchar+values/eplan4.png"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.2009.04.01.Fast+searching+of+large+_2800_n_2900_varchar+values/eplan4.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Last but not least, the cleanup scripts.&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;drop table [IndexedData_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;drop function dbo.[fn_hashbytesMAX_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;drop function dbo.[fn_SplitEveryX_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;drop table [util_Nums_361A370A-D881-4612-BE1C-916BD466E884]&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;I hope that you found this article useful.&amp;nbsp; It truly shows the power of indexes and provides a good workaround for some of the limitations of SQL Server.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;If you feel that you may be able to make use of this method for searching large data strings, but are not completely sure of how exactly this can be implemented in your system, contact me and I can see how SQL Server Nation may be able to further assist you.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;To download the full SQL Script used in this article, &lt;a title="Click Here" href="http://sqlservernation.com/media/p/172.aspx"&gt;click here&lt;/a&gt;.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://sqlservernation.com/aggbug.aspx?PostID=171" width="1" height="1"&gt;</content><author><name>BrandonGalderisi</name><uri>http://sqlservernation.com/members/BrandonGalderisi/default.aspx</uri></author><category term="Advanced SQL" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Advanced+SQL/default.aspx" /><category term="Indexes" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Indexes/default.aspx" /></entry><entry><title>Comparing a Sub-Query and a UDF</title><link rel="alternate" type="text/html" href="/blogs/tipweek/archive/2009/03/04/comparing-joins-vs-udf-calls.aspx" /><id>/blogs/tipweek/archive/2009/03/04/comparing-joins-vs-udf-calls.aspx</id><published>2009-03-04T14:40:00Z</published><updated>2009-03-04T14:40:00Z</updated><content type="html">&lt;p&gt;There has always been a lot of confusion in terms of performance between correlated sub-queries vs user-defined functions.&amp;nbsp; So, I&amp;#39;ve taken it upon myself to try to figure out which one is more effecient.&amp;nbsp; The results I have found are by no means conclusive, but will hopefully give you an idea as to when to use which approach as well as a general methodology for how to compare queries for effeciency purposes.&lt;/p&gt;
&lt;p&gt;First, I&amp;#39;ll start by creating a scalar user defined function that accepts the EmployeeID and the RateChangeDate and returns the SUM of the Rate field from EmployeePayHistory table in the AdventureWorks database.&amp;nbsp; This is a pretty common type of scalar valued user-defined function (UDF) used to return month-to-date and year-to-date style data.&lt;/p&gt;
&lt;pre style="border:1px solid #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 FUNCTION dbo.udf_GetPayHistory&lt;br /&gt;(&lt;br /&gt;    @EmployeeID INT,&lt;br /&gt;    @RateChangeDate DATETIME&lt;br /&gt;)&lt;br /&gt;RETURNS MONEY&lt;br /&gt;AS&lt;br /&gt;BEGIN&lt;br /&gt;    DECLARE @PayHistory MONEY&lt;br /&gt;&lt;br /&gt;    SET @PayHistory = &lt;br /&gt;    (&lt;br /&gt;        SELECT    SUM(Rate)&lt;br /&gt;        FROM HumanResources.EmployeePayHistory eh&lt;br /&gt;        WHERE &lt;br /&gt;            eh.EmployeeID = @EmployeeID AND&lt;br /&gt;            eh.RateChangeDate &amp;gt;= @RateChangeDate&lt;br /&gt;    )&lt;br /&gt;    RETURN(@PayHistory)&lt;br /&gt;END&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;In the code snippet below I query the HumanResources.Employee table in the AdventureWorks database and pass the EmployeeID and a static date to the function I defined above.&amp;nbsp; &lt;/p&gt;
&lt;pre style="border:1px solid #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;SELECT &lt;br /&gt;    EmployeeID, &lt;br /&gt;    LoginID, &lt;br /&gt;    dbo.udf_GetPayHistory(e.EmployeeID,&amp;#39;1/1/1997&amp;#39;)&lt;br /&gt;FROM &lt;br /&gt;    HumanResources.Employee e&lt;br /&gt;WHERE &lt;br /&gt;    EmployeeID BETWEEN 100 AND 200&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;The statement below uses a &lt;a title="Correlated Sub-query" href="http://articles.techrepublic.com.com/5100-10878_11-6100447.html"&gt;correlated sub-query&lt;/a&gt; to return the same information as the statement above which uses the UDF to return data.&amp;nbsp; The sub-query is correlated in that the HumanResources.EmployeePayHistory table access HumanResources.Employee table, which is table being queried at the outermost level.&amp;nbsp; So, although this statement operates as a batch statement, you can almost think of it as processing iteratively.&amp;nbsp; The inner query passing data to the outer query until the outer query satisfies its WHERE condition.&lt;/p&gt;
&lt;pre style="border:1px solid #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;DECLARE @RateChangeDate DATETIME&lt;br /&gt;SET @RateChangeDate = &amp;#39;1/1/1997&amp;#39;&lt;br /&gt;&lt;br /&gt;SELECT &lt;br /&gt;    EmployeeID, &lt;br /&gt;    LoginID,&lt;br /&gt;    PayHistory = &lt;br /&gt;    (&lt;br /&gt;        SELECT    SUM(Rate)&lt;br /&gt;        FROM HumanResources.EmployeePayHistory eh&lt;br /&gt;        WHERE &lt;br /&gt;            e.EmployeeID = eh.EmployeeID AND&lt;br /&gt;            eh.RateChangeDate &amp;gt;= @RateChangeDate&lt;br /&gt;    ) &lt;br /&gt;FROM HumanResources.Employee e&lt;br /&gt;WHERE EmployeeID BETWEEN 100 AND 200&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;
If I run the statements together and view the Actual Execution Plan for the two statements, it is the sub-query approach that takes up 75% of the processing time.&amp;nbsp; If I were to solely go by this information, I would think that the UDF approach to this solution is actually a LOT faster than the sub-query approach.&amp;nbsp; To confirm this assumption, I&amp;#39;ll need to run a couple of other tests.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.UDFPerformance/UDFPerformance_5F00_ExecutionPlanComparison.JPG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.UDFPerformance/UDFPerformance_5F00_ExecutionPlanComparison.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Below is a comparison of the &lt;a title="STATISTICS IO" href="http://blogs.techrepublic.com.com/datacenter/?p=179"&gt;STATISTICS IO&lt;/a&gt; statement between the two statements.&amp;nbsp; Because the two statements access the same two tables, I expect STATISTICS IO to show me the same information for the two statements, but this is NOT the case.&amp;nbsp; The statement that uses the UDF only includes reads for the Employee table, while the statement that uses the correlated sub-query involves both tables.&amp;nbsp; Clearly the 2nd statement is slower because it is showing more reads occurring on the database, right?&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/UDFPerformance_5F00_StatisticsIO.JPG"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.UDFPerformance/UDFPerformance_5F00_StatisticsIO.JPG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.UDFPerformance/UDFPerformance_5F00_StatisticsIO.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Next, I want to look at the exact time it takes for each of these statements to run.&amp;nbsp; You always have to be careful when comparing time between statements.&amp;nbsp; As a comparison metric, time is always dependent upon other factors on your database system at the time of running, such as load, memory pressure, etc.&amp;nbsp; The figure can change with each run.&amp;nbsp; However, since I&amp;#39;ll be running these two statements at approximately the same time on the same system, I am comfortable with their approximate results.&lt;/p&gt;
&lt;p&gt;Below is the screenshot for the subquery approach.&amp;nbsp; This query only takes 1ms.&amp;nbsp; I had run this query several times, so the execution plan had already been generated, which does help in execution time.&amp;nbsp; This query seems pretty fast.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.UDFPerformance/UDFPerformance_5F00_Subquery_5F00_StatisticsTime.JPG"&gt;&lt;/a&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.UDFPerformance/UDFPerformance_5F00_Subquery_5F00_StatisticsTime1.JPG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.UDFPerformance/UDFPerformance_5F00_Subquery_5F00_StatisticsTime1.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.UDFPerformance/UDFPerformance_5F00_Subquery_5F00_StatisticsTime.JPG"&gt;&lt;br /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Below is the STATISTICS TIME for the UDF approach.&amp;nbsp; Notice that using the UDF requires just a fraction more time.&amp;nbsp; Ultimately, the time differences is insignificant since there is such a slight variation.&amp;nbsp; Even though I ran the statements several times and received the same numbers each time, I&amp;#39;m still not really sure which statement is more effecient.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.UDFPerformance/UDFPerformance_5F00_UDF_5F00_StatisticsTime1.JPG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.UDFPerformance/UDFPerformance_5F00_UDF_5F00_StatisticsTime1.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So, my next step is to look at the statement results in SQL Server Profiler.&amp;nbsp; I&amp;#39;m mainly interested in the Reads and Duration columns from Profiler.&amp;nbsp; The results from the subquery are below.&amp;nbsp; The number of reads from this approach match up very closely to the results from STATISTICS IO, which is what I expect.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/UDFPerformance_5F00_Profiler_5F00_SubQuery.JPG"&gt;&lt;/a&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.UDFPerformance/UDFPerformance_5F00_Profiler_5F00_SubQuery.JPG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.UDFPerformance/UDFPerformance_5F00_Profiler_5F00_SubQuery.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;However, when I view the results from using the user-defined function, I get results that I am not expecting.&amp;nbsp; The number of reads do not match up at all with what was returned from STATISTICS IO.&amp;nbsp; The results returned from Profiler shows the ENTIRE number of reads for the query statement.&amp;nbsp; The part that is misleading is that Profiler is showing all reads that the UDF incurrs.&amp;nbsp; So, even though it is being showed as being called once, there are more reads involved in the database through its use.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.UDFPerformance/UDFPerformance_5F00_Profiler_5F00_UDF.JPG"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.UDFPerformance/UDFPerformance_5F00_Profiler_5F00_UDF.JPG" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Results&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Today I looked at a performance comparison between using a correlated sub-query and a user-defined function (UDF).&amp;nbsp; When I use the tools I am accustomed to using in terms of determining statement effeciency, I was misled.&amp;nbsp; I had to dig a bit depper and break out SQL Server Profiler to really determine which statement was faster; the correlated sub-query.&amp;nbsp; While the sub-query is faster, it does not promote code re-use like the UDF does, so there is a trade off.&amp;nbsp; If I were to use the logic over and over again, I would consider the performance trade off of using the UDF so I didn&amp;#39;t have to rewrite the logic over and over again.&amp;nbsp; It really depends on the situation you&amp;#39;re faced with to determine which approach to use.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&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=57" width="1" height="1"&gt;</content><author><name>TimChapman</name><uri>http://sqlservernation.com/members/TimChapman/default.aspx</uri></author><category term="Intermediate SQL" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Intermediate+SQL/default.aspx" /><category term="Beginner SQL" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Beginner+SQL/default.aspx" /><category term="User Defined Functions" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/User+Defined+Functions/default.aspx" /></entry><entry><title>How to allow multiple NULL values in a UNIQUE column</title><link rel="alternate" type="text/html" href="/blogs/tipweek/archive/2009/02/25/how-to-allow-multiple-null-values-in-a-unique-column.aspx" /><id>/blogs/tipweek/archive/2009/02/25/how-to-allow-multiple-null-values-in-a-unique-column.aspx</id><published>2009-02-25T19:42:00Z</published><updated>2009-02-25T19:42:00Z</updated><content type="html">&lt;p&gt;SQL Server can guarantee unique values in two ways, Primary keys and a unique indexes/constraints.&amp;nbsp; The primary difference between the two is that fields bound to a unique index or constraint can be NULL while a primary key field can not.&amp;nbsp; For the purpose of the unique indexes and constraints, NULL values are treated equal.&amp;nbsp; That means that you cannot have two NULL values.&lt;br /&gt;&lt;br /&gt;Fortunately, there is a workaround for that will allow you to store multiple NULL values while ensuring that non-NULL values are unique.&amp;nbsp; We will utilize a schema bound view in order to facilitate this.&amp;nbsp; We will also create an index on the startdate column to help with the performance of the indexed view.&lt;br /&gt;&lt;br /&gt;First let&amp;#39;s create a table to test with.&amp;nbsp; In order to prevent conflicts with existing tables and databases, we suggest that you create these objects either in TempDB or in a database specific for testing.&amp;nbsp; In case you do not have the privileges to create your own database, we have added a partial GUID to all of the object names.&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;CREATE TABLE EMPLOYEE_9F2E916313EB&lt;br /&gt;  (employee_id int not null identity(1,1)&lt;br /&gt;  ,employee_lastname nvarchar(32) not null&lt;br /&gt;  ,employee_firstname nvarchar(32) not null&lt;br /&gt;  ,employee_startdate datetime null&lt;br /&gt;  ,constraint pk_EMPLOYEE_9F2E916313EB primary key clustered (employee_id)&lt;br /&gt;  )&lt;br /&gt;GO&lt;br /&gt;create index idx_EMPLOYEE_9F2E916313EB_startdate on EMPLOYEE_9F2E916313EB (employee_startdate)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;While this may not be a very practical example, let&amp;#39;s pretend that we want no two employees to have the same start date.&amp;nbsp; But, we may not know the start date of certain employees so the column has to allow null values.&lt;br /&gt;&lt;br /&gt;Next let&amp;#39;s create the view and the UNIQUE CLUSTERED&amp;nbsp;index.&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;CREATE VIEW EMPLOYEE_9F2E916313EB_vw_startdate&lt;br /&gt;with schemabinding&lt;br /&gt;as&lt;br /&gt;select employee_startdate&lt;br /&gt;from dbo.EMPLOYEE_9F2E916313EB&lt;br /&gt;where employee_startdate is not null&lt;br /&gt;GO&lt;br /&gt;create unique clustered index uidx_EMPLOYEE_9F2E916313EB_vw_startdate_startdate &lt;br /&gt;  on dbo.EMPLOYEE_9F2E916313EB_vw_startdate (employee_startdate)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;&lt;br /&gt;An important thing to note is that you must specify the two part name for the object.&amp;nbsp; For SQL Server 2000 you need to specify the object owner, for SQL Server 2005 you must specify the schema.&amp;nbsp; The key to making a schema bound view is the option WITH SCHEMABINDING in the views declaration.&amp;nbsp; Also, an indexed view MUST have a clustered index created on it.&lt;br /&gt;&lt;br /&gt;Now let&amp;#39;s see how the database allows for multiple NULL values.&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;insert into EMPLOYEE_9F2E916313EB(employee_lastname,employee_firstname,employee_startdate)&lt;br /&gt;values (&amp;#39;Smith&amp;#39;,&amp;#39;John&amp;#39; ,NULL)&lt;br /&gt;insert into EMPLOYEE_9F2E916313EB(employee_lastname,employee_firstname,employee_startdate)&lt;br /&gt;values (&amp;#39;Smith&amp;#39;,&amp;#39;Mike&amp;#39;,NULL)&lt;br /&gt;GO&lt;br /&gt;select * from dbo.EMPLOYEE_9F2E916313EB&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;The result should look like this:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.How+to+allow+multiple+NULL+values+in+a+UNIQUE+column/01_5F00_2_5F00_rec.png"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.How+to+allow+multiple+NULL+values+in+a+UNIQUE+column/01_5F00_2_5F00_rec.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Now we will add more data that contains non-NULL dates.&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;insert into EMPLOYEE_9F2E916313EB(employee_lastname,employee_firstname,employee_startdate)&lt;br /&gt;values (&amp;#39;Smith&amp;#39;,&amp;#39;Tim&amp;#39;,&amp;#39;2009-02-19&amp;#39;)&lt;br /&gt;insert into EMPLOYEE_9F2E916313EB(employee_lastname,employee_firstname,employee_startdate)&lt;br /&gt;values (&amp;#39;Smith&amp;#39;,&amp;#39;Bob&amp;#39;,&amp;#39;2009-02-20&amp;#39;)&lt;br /&gt;go&lt;br /&gt;select * from dbo.EMPLOYEE_9F2E916313EB&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;The results should look like this:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek/02_5F00_4_5F00_rec.png"&gt;&lt;/a&gt;&lt;a href="http://sqlservernation.com/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.How+to+allow+multiple+NULL+values+in+a+UNIQUE+column/02_5F00_4_5F00_rec.png"&gt;&lt;img src="http://sqlservernation.com/resized-image.ashx/__size/550x0/__key/CommunityServer.Blogs.Components.WeblogFiles/tipweek.How+to+allow+multiple+NULL+values+in+a+UNIQUE+column/02_5F00_4_5F00_rec.png" border="0" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div style="padding-right:0pt;padding-left:0pt;padding-bottom:1em;padding-top:1em;text-align:left;" id="h1l6"&gt;&lt;br /&gt;Now let&amp;#39;s try inserting another record for 20-Feb-2009.&lt;br /&gt;&lt;br /&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;insert into EMPLOYEE_9F2E916313EB(employee_lastname,employee_firstname,employee_startdate)&lt;br /&gt;values(&amp;#39;Smith&amp;#39;,&amp;#39;Ralph&amp;#39;,&amp;#39;2009-02-20&amp;#39;)&lt;br /&gt;go&lt;br /&gt;select * from dbo.EMPLOYEE_9F2E916313EB&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;&lt;br /&gt;You will see that the same four records still exist and you would have received the following error.&lt;br /&gt;&lt;br /&gt;&lt;span style="color:#ff0000;"&gt;Msg 2601, Level 14, State 1, Line 1&lt;/span&gt;&lt;br style="color:#ff0000;" /&gt;&lt;span style="color:#ff0000;"&gt;Cannot insert duplicate key row in object &amp;#39;dbo.EMPLOYEE_9F2E916313EB_vw_startdate&amp;#39; with unique index &amp;#39;uidx_EMPLOYEE_9F2E916313EB_vw_startdate_startdate&amp;#39;.&lt;/span&gt;&lt;br /&gt;The statement has been terminated.&lt;br /&gt;&lt;br /&gt;The schema bound view has prevented us from having multiple values of 20-Feb-2009 while still allowing multiple NULL values.&lt;br /&gt;&lt;br /&gt;You must be aware that the schema bound view will prevent some operations being performed on the table.&amp;nbsp; You can add new columns and drop existing columns but before you would be able to change the column referenced in the view, the view must be dropped.&amp;nbsp; Below, the first two statements will work, but the third one will fail with the error below.
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;alter table EMPLOYEE_9F2E916313EB ADD login_name nvarchar(32) null&lt;br /&gt;go&lt;br /&gt;alter table EMPLOYEE_9F2E916313EB drop column employee_lastname&lt;br /&gt;go&lt;br /&gt;alter table EMPLOYEE_9F2E916313EB alter column employee_startdate datetime null&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;&lt;span style="color:#ff0000;"&gt;Msg 5074, Level 16, State 1, Line 1&lt;/span&gt;&lt;br style="color:#ff0000;" /&gt;&lt;span style="color:#ff0000;"&gt;The object &amp;#39;EMPLOYEE_9F2E916313EB_vw_startdate&amp;#39; is dependent on column &amp;#39;employee_startdate&amp;#39;.&lt;/span&gt;&lt;br style="color:#ff0000;" /&gt;&lt;span style="color:#ff0000;"&gt;Msg 4922, Level 16, State 9, Line 1&lt;/span&gt;&lt;br style="color:#ff0000;" /&gt;&lt;span style="color:#ff0000;"&gt;ALTER TABLE ALTER COLUMN employee_startdate failed because one or more objects access this column.&lt;/span&gt;&lt;br style="color:#ff0000;" /&gt;&lt;br /&gt;&lt;br /&gt;Cleanup scripts.&lt;br /&gt;
&lt;pre style="border:1px dashed #999999;padding:5px;overflow:auto;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;color:#000000;background-color:#eeeeee;font-size:12px;line-height:14px;width:100%;"&gt;&lt;code&gt;drop view EMPLOYEE_9F2E916313EB_vw_startdate&lt;br /&gt;drop table EMPLOYEE_9F2E916313EB&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;span style="font-size:x-small;"&gt;&lt;br /&gt;&lt;span style="color:#000000;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://sqlservernation.com/aggbug.aspx?PostID=48" width="1" height="1"&gt;</content><author><name>BrandonGalderisi</name><uri>http://sqlservernation.com/members/BrandonGalderisi/default.aspx</uri></author><category term="NULL" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/NULL/default.aspx" /><category term="schemabinding" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/schemabinding/default.aspx" /><category term="unique" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/unique/default.aspx" /><category term="views" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/views/default.aspx" /><category term="constraint" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/constraint/default.aspx" /></entry><entry><title>Viewing SQL Server Jobs and History</title><link rel="alternate" type="text/html" href="/blogs/tipweek/archive/2009/02/25/viewing-sql-server-jobs-and-history.aspx" /><id>/blogs/tipweek/archive/2009/02/25/viewing-sql-server-jobs-and-history.aspx</id><published>2009-02-25T19:05:00Z</published><updated>2009-02-25T19:05:00Z</updated><content type="html">&lt;p&gt;In SQL Server 2000, it was cumbersome to allow those who were not members of the sysadmin group the ability to view SQL Agent Jobs and job history through Enterprise Manager.&amp;nbsp; SQL Server 2005 has made this a lot easier through the use of specialized database roles in the msdb database for viewing such information.&amp;nbsp; In the scripts below, I get around these SQL 2000 restrictions with a couple of custom objects that allow you to view SQL Agent Job status and history.&lt;/p&gt;
&lt;p&gt;First, I need to create a function that formats the job date related data from the SQL Server system tables so that it is in a format that is a bit easier on the eyes.&amp;nbsp; I am creating this function in the msdb database because this is where the system tables are located.&amp;nbsp; If you aren&amp;#39;t already created backups of your msdb database, now is a really good time to start.&lt;/p&gt;
&lt;pre style="font-size:12px;overflow:auto;width:100%;color:#000000;line-height:14px;font-family:Andale Mono,Lucida Console,Monaco,fixed,monospace;background-color:#eeeeee;border:#999999 1px solid;padding:5px;"&gt;&lt;code&gt;USE [msdb]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE FUNCTION [dbo].[fn_GetJobDate]
(
 @LastRunDate INT,
 @LastRunTime INT
)
RETURNS SMALLDATETIME
AS BEGIN
      DECLARE @Date SMALLDATETIME
      SET @Date = 
        CASE WHEN @LastRunTime &amp;gt; 0
        THEN CAST((SUBSTRING(CAST(@LastRunDate AS VARCHAR(10)),
                    5, 2) + &amp;#39;/&amp;#39;
                    + RIGHT(CAST(@LastRunDate AS VARCHAR(10)), 2)
                    + &amp;#39;/&amp;#39;
                    + LEFT(CAST(@LastRunDate AS VARCHAR(10)), 4)
                    + &amp;#39; &amp;#39;
                    + CAST(CASE WHEN SUBSTRING(RIGHT(STUFF(&amp;#39; &amp;#39;, 1, 1, &amp;#39;000000&amp;#39;) + 
                            CONVERT(VARCHAR(6), @LastRunTime), 6), 1, 2) &amp;gt; 12
                      THEN SUBSTRING(RIGHT(STUFF(&amp;#39; &amp;#39;, 1, 1, &amp;#39;000000&amp;#39;) + 
                            CONVERT(VARCHAR(6), @LastRunTime), 6), 1, 2) - 12
                      ELSE SUBSTRING(RIGHT(STUFF(&amp;#39; &amp;#39;, 1, 1, &amp;#39;000000&amp;#39;) + 
                            CONVERT(VARCHAR(6), @LastRunTime), 6), 1, 2)
                    END AS VARCHAR(2)) + &amp;#39;:&amp;#39;
                    + SUBSTRING(RIGHT(STUFF(&amp;#39; &amp;#39;, 1, 1, &amp;#39;000000&amp;#39;)
                            + convert(varchar(6), @LastRunTime),
                            6), 3, 2) + &amp;#39;:&amp;#39;
                    + SUBSTRING(RIGHT(STUFF(&amp;#39; &amp;#39;, 1, 1, &amp;#39;000000&amp;#39;)
                            + convert(varchar(6), @LastRunTime),
                            6), 5, 2)) AS SMALLDATETIME)
        ELSE NULL
        END

      RETURN (@Date)
   END
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that I have my function created in msdb for formatting the dates, I can create my stored procedure that pulls the job status and history.&amp;nbsp; This procedure is also created in msdb, and accepts one parameter, the Job Name.&amp;nbsp; You can provide the name of a specific job and it will give you job-specific details.&amp;nbsp; If you omit the value for the @JobName parameter, it will return information for all jobs for the SQL Agent.&amp;nbsp;&lt;/p&gt;
&lt;pre style="font-size:12px;overflow:auto;width:100%;color:#000000;line-height:14px;font-family:Andale Mono, Lucida Console, Monaco, fixed, monospace;background-color:#eee;border:#999999 1px solid;padding:5px;"&gt;&lt;code&gt;USE msdb
GO

CREATE PROCEDURE usp_GetJobInformation
(
    @JobName VARCHAR(255) = NULL
)
AS
BEGIN
    IF OBJECT_ID(&amp;#39;tempdb..#Results&amp;#39;)&amp;gt;0
        DROP TABLE #Results
   
    CREATE TABLE #Results
    (
        job_id                    UNIQUEIDENTIFIER NOT NULL,
        last_run_date             INT              NOT NULL,
        last_run_time             INT              NOT NULL,
        next_run_date             INT              NOT NULL,
        next_run_time             INT              NOT NULL,
        next_run_schedule_id      INT              NOT NULL,
        requested_to_run          INT              NOT NULL,
        request_source            INT              NOT NULL,
        request_source_id         SYSNAME   COLLATE DATABASE_DEFAULT NULL,
        running                   INT              NOT NULL,
        current_step              INT              NOT NULL,
        current_retry_attempt     INT              NOT NULL,
        job_state                 INT              NOT NULL
    )
   
    DECLARE @JobID VARCHAR(100)
    SELECT TOP 1 @JobID = job_ID FROM msdb.dbo.sysjobs
   
    INSERT INTO #Results
    EXECUTE master.dbo.xp_sqlagent_enum_jobs 1, @JobID
   
    SELECT
        s.Name,
        CASE WHEN s.enabled = 0 THEN &amp;#39;No&amp;#39; ELSE &amp;#39;Yes&amp;#39; END AS Enabled,
        CASE WHEN next_run_date &amp;gt; 0 THEN &amp;#39;Yes&amp;#39; ELSE &amp;#39;No&amp;#39; END AS Scheduled,
        sc.name AS Category,
        current_step AS CurrentExecutionStep,
        msdb.dbo.fn_GetJobDate(last_run_date,last_run_time) AS LastRunDate,
        msdb.dbo.fn_GetJobDate(next_run_date, next_run_time) AS NextRunDate,
        CASE WHEN xp.running = 0 THEN &amp;#39;Not Running&amp;#39; ELSE &amp;#39;Executing...&amp;#39; END AS Status,
        ISNULL((
            SELECT 
                CASE 
                    WHEN run_status = 1 THEN &amp;#39;Succeeded&amp;#39; 
                    WHEN run_status = 3 THEN &amp;#39;Cancelled&amp;#39; 
                    WHEN run_status = 0 THEN &amp;#39;Failed&amp;#39; 
                    WHEN run_status IS NULL THEN &amp;#39;Unknown&amp;#39; 
                END AS LastRunStatus
            FROM
                msdb..sysjobhistory sho
            WHERE
                sho.job_id = xp.job_id AND
                sho.instance_id =
                (
                    SELECT MAX(instance_id)
                    FROM msdb..sysjobhistory sj (NOLOCK)
                    WHERE sj.job_id = sho.job_id
                )
        ) ,&amp;#39;Unknown&amp;#39;) AS LastRunStatus
    FROM     #Results xp    
        INNER JOIN msdb..sysjobs s on xp.job_id = s.job_id
        INNER JOIN msdb..syscategories sc on s.category_id = sc.category_id
    WHERE
        s.Name = ISNULL(@JobName, s.Name)
    ORDER BY s.Name

    BEGIN
    IF OBJECT_ID(&amp;#39;tempdb..#JobHistory&amp;#39;)&amp;gt;0
        DROP TABLE #JobHistory
   

        CREATE TABLE #JobHistory
        (
            JobName SYSNAME,
            StepID INT,
            StepName SYSNAME,
            Message NVARCHAR(1024),
            RunStatus INT,
            RunDate INT,
            RunTime INT ,
            RunDuration INT    ,
            operator_emailed NVARCHAR(20),
            operator_netsent NVARCHAR(20),
            operator_paged NVARCHAR(20)               
        )

        INSERT INTO #JobHistory
        SELECT
            sjj.Name,
            sjh.step_id,
            sjh.step_name,
            sjh.message,
            sjh.run_status,
            sjh.run_date,
            sjh.run_time,
            sjh.run_duration,
            operator_emailed = so1.name,
            operator_netsent = so2.name,
            operator_paged = so3.name
        FROM
            msdb.dbo.sysjobhistory                sjh
            JOIN msdb.dbo.sysjobs sjj ON sjh.job_id = sjj.job_id
            LEFT OUTER JOIN msdb.dbo.sysoperators so1  ON (sjh.operator_id_emailed = so1.id)
            LEFT OUTER JOIN msdb.dbo.sysoperators so2  ON (sjh.operator_id_netsent = so2.id)
            LEFT OUTER JOIN msdb.dbo.sysoperators so3  ON (sjh.operator_id_paged = so3.id),
            msdb.dbo.sysjobs                 sj
        WHERE
        (sj.job_id = sjh.job_id)

        SELECT
            JobName, StepID, StepName, 
            Message, 
            msdb.dbo.fn_GetJobDate(RunDate, RunTime) AS LastRunTime,
            CASE RunStatus
                WHEN 0 THEN &amp;#39;Failed&amp;#39;
                WHEN 1 THEN &amp;#39;Succeeded&amp;#39;
                WHEN 2 THEN &amp;#39;Retry (step only)&amp;#39;
                WHEN 3 THEN &amp;#39;Canceled&amp;#39;
                WHEN 4 THEN &amp;#39;In-progress message&amp;#39;
                WHEN 5 THEN &amp;#39;Unknown&amp;#39;
            END AS RunStatus
        FROM #JobHistory
        ORDER BY JobName, LastRunTime DESC, StepID ASC
    END

END


GO
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In terms of security for the above stored procedure, you&amp;#39;ll need to add any user or group for which you want to run the procedure as a User in the msdb database.&amp;nbsp; You&amp;#39;ll also need to give this user EXECUTE permissions on the stored procedure.&amp;nbsp; &lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://sqlservernation.com/aggbug.aspx?PostID=47" width="1" height="1"&gt;</content><author><name>TimChapman</name><uri>http://sqlservernation.com/members/TimChapman/default.aspx</uri></author><category term="jobs" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/jobs/default.aspx" /><category term="job history" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/job+history/default.aspx" /><category term="Intermediate SQL" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Intermediate+SQL/default.aspx" /></entry><entry><title>Server Role Members</title><link rel="alternate" type="text/html" href="/blogs/tipweek/archive/2009/02/25/server-role-members.aspx" /><id>/blogs/tipweek/archive/2009/02/25/server-role-members.aspx</id><published>2009-02-25T15:53:00Z</published><updated>2009-02-25T15:53:00Z</updated><content type="html">&lt;p&gt;Below is a quick and easy way to query SQL Server 2005 system views to determine which users belong to which server roles on the instance.&lt;/p&gt;
&lt;pre style="font-size:12px;overflow:auto;width:100%;color:#000000;line-height:14px;font-family:Andale Mono, Lucida Console, Monaco, fixed, monospace;background-color:#eee;border:#999999 1px solid;padding:5px;"&gt;&lt;code&gt;SELECT
      rolename = rolep.name,
      membername = memp.name
FROM
      sys.server_role_members rm
      JOIN sys.server_principals rolep ON rm.role_principal_id = rolep.principal_id
      JOIN sys.server_principals memp ON rm.member_principal_id = memp.principal_id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;To determine which users are members of your database level roles on your instance, &lt;a target="_blank" href="http://sqlservernation.com/blogs/tipweek/archive/2009/02/25/database-role-members.aspx" title="Database Roles"&gt;see this article&lt;/a&gt;.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://sqlservernation.com/aggbug.aspx?PostID=46" width="1" height="1"&gt;</content><author><name>TimChapman</name><uri>http://sqlservernation.com/members/TimChapman/default.aspx</uri></author><category term="Intermediate SQL" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Intermediate+SQL/default.aspx" /><category term="Server Roles" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Server+Roles/default.aspx" /></entry><entry><title>Database Role Members</title><link rel="alternate" type="text/html" href="/blogs/tipweek/archive/2009/02/25/database-role-members.aspx" /><id>/blogs/tipweek/archive/2009/02/25/database-role-members.aspx</id><published>2009-02-25T15:52:00Z</published><updated>2009-02-25T15:52:00Z</updated><content type="html">&lt;p&gt;Below is a quick and easy way to query SQL Server 2005 system views to determine which users belong to which database roles on your SQL Server databases.&lt;/p&gt;
&lt;pre style="font-size:12px;overflow:auto;width:100%;color:#000000;line-height:14px;font-family:Andale Mono, Lucida Console, Monaco, fixed, monospace;background-color:#eee;border:#999999 1px solid;padding:5px;"&gt;&lt;code&gt;SELECT
      rolename = rolep.name,
      membername = memp.name
FROM
      sys.database_role_members rm
      JOIN sys.database_principals rolep ON rm.role_principal_id = rolep.principal_id
      JOIN sys.database_principals memp ON rm.member_principal_id = memp.principal_id

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To determine which users are members of your server level roles on your instance, &lt;a target="_blank" href="http://sqlservernation.com/blogs/tipweek/archive/2009/02/25/server-role-members.aspx" title="Server Roles"&gt;see this article&lt;/a&gt;.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://sqlservernation.com/aggbug.aspx?PostID=45" width="1" height="1"&gt;</content><author><name>TimChapman</name><uri>http://sqlservernation.com/members/TimChapman/default.aspx</uri></author><category term="Intermediate SQL" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Intermediate+SQL/default.aspx" /><category term="Beginner SQL" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Beginner+SQL/default.aspx" /><category term="Advanced SQL" scheme="http://sqlservernation.com/blogs/tipweek/archive/tags/Advanced+SQL/default.aspx" /></entry></feed>