<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:blogger="http://schemas.google.com/blogger/2008" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;D0EERX88eyp7ImA9WhBWEUg.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422</id><updated>2013-04-05T22:53:24.173+13:00</updated><category term="Command Line" /><category term="LazyDBA" /><category term="alias" /><category term="templates" /><category term="CLR" /><category term="SQL Agent" /><category term="DNS" /><category term="schema compare" /><category term="tsql" /><category term="en" /><category term="dimension" /><category term="#tsql2sday" /><category term="SQLServer" /><category term="functions" /><category term="TSQL tips" /><category term="Firewall" /><category term="date" /><category term="full text search" /><category term="CNAMES" /><category term="blogged" /><category term="sequences" /><category term="TSQL Tuesday" /><category term="PowerShell" /><category term="jobs" /><category term="identity" /><category term="code camp" /><category term="telnet" /><category term="Easter" /><category term="port" /><category term="1433" /><category term="1434" /><category term="SSDT" /><category term="training" /><category term="comments" /><category term="presentations" /><title>SQL Concepts</title><subtitle type="html">I blog about SQL Server and related technologies.</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://blog.sqlconcepts.co.nz/" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>23</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/SQLConcepts" /><feedburner:info uri="sqlconcepts" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><feedburner:emailServiceId>SQLConcepts</feedburner:emailServiceId><feedburner:feedburnerHostname>http://feedburner.google.com</feedburner:feedburnerHostname><entry gd:etag="W/&quot;D0EERX8zeCp7ImA9WhBWEUg.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-3001065726615067119</id><published>2013-04-05T22:53:00.001+13:00</published><updated>2013-04-05T22:53:24.180+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2013-04-05T22:53:24.180+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SSDT" /><category scheme="http://www.blogger.com/atom/ns#" term="en" /><category scheme="http://www.blogger.com/atom/ns#" term="SQLServer" /><category scheme="http://www.blogger.com/atom/ns#" term="blogged" /><title>SSDT syntax highlighting disappeared. How I fixed it.</title><content type="html">I recently struggled with a problem where I seemed to lose the syntax highlighting in Visual Studio 2012 for SSDT projects.&amp;nbsp; Took me a while to stumble onto a solution after multiple reinstalls and almost giving up to rebuild the PC (I like syntax highlighting)&lt;br /&gt;
&lt;br /&gt;
The tldr solution is:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Open a Visual Studio Command Prompt (as administrator)&lt;/li&gt;
&lt;li&gt;Run:&lt;br /&gt;DEVENV /SETUP&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
The problem (no pretty colours)&lt;/h3&gt;
I'd been having an issue with my PC build where my SQL 2012 installation seems to lose some WMI settings.&amp;nbsp; I can temporarily fix that with a mofcomp command, but getting frustrated with that I decided to reinstall SQL Server 2012.&amp;nbsp; That was my first mistake.&amp;nbsp; After the reinstall of SQL Server 2012 I noticed I'd lost all the syntax highlighting for SSDT projects as per this Stack Overflow question (not mine by the way): http://stackoverflow.com/questions/14365678/limited-vs2012-sql-syntax-highlighting&lt;br /&gt;
&lt;br /&gt;
That article has the rather unhelpful "Hey it just fixed itself" answer.&amp;nbsp; No such luck for me.&amp;nbsp; After a general failing of my Google foo, I thought it would be just easier to reinstall Visual Studio 2012, SSDT etc.&amp;nbsp; So that's what I did.&amp;nbsp; Alas I still had no syntax highlighting.&amp;nbsp; And no options for the SQL "types" to manually update the colours.&lt;br /&gt;
&lt;br /&gt;
To list all I had tried;&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Removed and reinstalled SQL Server 2012&lt;/li&gt;
&lt;li&gt;Patched SQL Server 2012 with SP1&lt;/li&gt;
&lt;li&gt;Reinstalled SSDT for Visual Studio 2012&lt;/li&gt;
&lt;li&gt;Removed and reinstalled Visual Studio 2012&lt;/li&gt;
&lt;li&gt;Patched Visual Studio 2012 with Update 1&lt;/li&gt;
&lt;li&gt;Tried an earlier version of SSDT&lt;/li&gt;
&lt;li&gt;Updated SSDT (again)&lt;/li&gt;
&lt;/ul&gt;
All yielded no difference.&lt;br /&gt;
&lt;br /&gt;
Twitter failed me too.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
The solution&lt;/h3&gt;
I had pretty much given up and decided to get the PC rebuilt.&amp;nbsp; But one last Google search yielded a couple of potential fixes that I thought I would try.&amp;nbsp; These were two further Stack Overflow articles about problems that were (at best) vaguely similar to mine:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;http://stackoverflow.com/questions/15643145/cannot-find-user-keywords-section-in-visual-studio-2012&lt;/li&gt;
&lt;li&gt;http://stackoverflow.com/questions/11988077/plain-c-sharp-editor-in-visual-studio-2012-no-intellisense-no-indentation-no&lt;/li&gt;
&lt;/ul&gt;
The second one looked more promising, but infinitely more difficult for a simple DBA.&amp;nbsp; So I thought I'd give the first article a go first.&lt;br /&gt;
&lt;br /&gt;
The first stack overflow article says to perform the following steps:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Open a Visual Studio Command Prompt (as administrator)&lt;/li&gt;
&lt;li&gt;Run:&lt;br /&gt;DEVENV /SETUP&lt;/li&gt;
&lt;/ul&gt;
After all my reinstallations I really couldn't see this making any difference.&amp;nbsp; I was happy to be wrong!&amp;nbsp; These simple steps brought back the options to update colours for syntax highlighting.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=q8BU87lH918:sTNMWz7coQ4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=q8BU87lH918:sTNMWz7coQ4:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/q8BU87lH918" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/3001065726615067119/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2013/04/ssdt-syntax-highlighting-disappeared.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/3001065726615067119?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/3001065726615067119?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/q8BU87lH918/ssdt-syntax-highlighting-disappeared.html" title="SSDT syntax highlighting disappeared. How I fixed it." /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2013/04/ssdt-syntax-highlighting-disappeared.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0EHQHsyeyp7ImA9WhBRE0U.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-209270117468233112</id><published>2013-03-04T22:33:00.003+13:00</published><updated>2013-03-04T22:33:51.593+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2013-03-04T22:33:51.593+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SSDT" /><category scheme="http://www.blogger.com/atom/ns#" term="dimension" /><category scheme="http://www.blogger.com/atom/ns#" term="SQLServer" /><title>Data warehouse starter for 10</title><content type="html">I've been working on a little project in my spare time, as scarce&amp;nbsp;as that is lately, and as part of that I was putting together some dimensions for a star schema. &amp;nbsp;This isn't the first time I've been around this block so I dug out some old code. &amp;nbsp;I also thought "I can't be the first to do, need or want this".&lt;br /&gt;
&lt;br /&gt;
So I took some of the common dimensions and code to populate them and started a &lt;a href="http://msdn.microsoft.com/en-us/data/tools.aspx"&gt;SSDT&lt;/a&gt; project.&amp;nbsp; I've added a few more dimensions since that and have since loaded it up to &lt;a href="http://www.bitbucket.org/"&gt;BitBucket&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
I've opened up the repository to allow anyone who is interested or would benefit from the beginnings of a data warehouse model.&amp;nbsp; &lt;b&gt;You can find it here: &lt;a href="https://bitbucket.org/kchenery/warehouse-model"&gt;https://bitbucket.org/kchenery/warehouse-model&lt;/a&gt;&lt;/b&gt;&lt;br /&gt;
&lt;h3&gt;
Schema&lt;/h3&gt;
Currently you'll find it has dimensions for:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Date&lt;/li&gt;
&lt;li&gt;Time&lt;/li&gt;
&lt;li&gt;Country&lt;/li&gt;
&lt;li&gt;Continent&lt;/li&gt;
&lt;li&gt;Currency&lt;/li&gt;
&lt;li&gt;HTTP and FTP status codes - thanks to David Curlewis (&lt;a href="http://curlewis.co.nz/"&gt;b&lt;/a&gt; | &lt;a href="https://twitter.com/databasedave"&gt;t&lt;/a&gt;) for the inspiration on these&lt;/li&gt;
&lt;li&gt;Languages&lt;/li&gt;
&lt;li&gt;Top Level Domains&lt;/li&gt;
&lt;/ul&gt;
If you're in New Zealand, you'll find a couple of specific dimensions:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;ANZSIC codes&lt;/li&gt;
&lt;li&gt;NZ Regions&lt;/li&gt;
&lt;/ul&gt;
Its far from complete.&amp;nbsp; So if you find something that needs fixing or like it but want it expanded feel free to &lt;a href="https://bitbucket.org/kchenery/warehouse-model/issues?status=new&amp;amp;status=open"&gt;log an issue/request&lt;/a&gt; in BitBucket and I'll see what I can do for you.&amp;nbsp; For example; I want to add currency symbols to the currency dimension (but haven't yet found a good source).&lt;br /&gt;
&lt;h3&gt;
Data&lt;/h3&gt;
Along with the schema to create the basic dimensions, I've included post deploy scripts in the SSDT project to pre-populate the data.&amp;nbsp; Most of these are pretty simple scripts that merge a long SELECT...UNION ALL script into the target dimension table.&lt;br /&gt;
&lt;br /&gt;
The Date dimension has a procedure that is called to create a range of dates.&amp;nbsp; This can be called to customise the range loaded or even extend that range.&lt;br /&gt;
&lt;br /&gt;
You'll also notice each dimension table has a "special" record to record the "unknown" dimension value.&amp;nbsp; The unknown record, and any other special records, will always have a negative value for the dimension key.&amp;nbsp; E.g. -1 is always the unknown record.&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
Holiday Dates&lt;/h4&gt;
Now a Date dimension, like any dimension or even database, is only as good as the data in it.&amp;nbsp; You can customise the dates by adding your current regional dates into the dimHolidayDate table.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=Jb8xIBvS-uc:GJHB_BYB1Y8:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=Jb8xIBvS-uc:GJHB_BYB1Y8:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/Jb8xIBvS-uc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/209270117468233112/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2013/03/data-warehouse-starter-for-10.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/209270117468233112?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/209270117468233112?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/Jb8xIBvS-uc/data-warehouse-starter-for-10.html" title="Data warehouse starter for 10" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2013/03/data-warehouse-starter-for-10.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUMFQHc7fSp7ImA9WhJbE00.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-7302373164119521542</id><published>2012-09-22T21:50:00.000+12:00</published><updated>2012-09-22T21:50:11.905+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-22T21:50:11.905+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SSDT" /><category scheme="http://www.blogger.com/atom/ns#" term="jobs" /><category scheme="http://www.blogger.com/atom/ns#" term="templates" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL Agent" /><title>SSDT SQL Agent Job Deployment Template</title><content type="html">SQL Server Data Tools (SSDT) is a great product for database development.&amp;nbsp; It even includes the ability to code and publish some "server level" objects:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Logins (so we can associate database users)&lt;/li&gt;
&lt;li&gt;Extended Events&lt;/li&gt;
&lt;li&gt;Linked Servers&lt;/li&gt;
&lt;li&gt;Cryptography keys&lt;/li&gt;
&lt;li&gt;Service Broker queues&lt;/li&gt;
&lt;li&gt;Server level triggers&lt;/li&gt;
&lt;li&gt;Server roles and memberships&lt;/li&gt;
&lt;/ul&gt;
But one object currently omitted is the ability to define and deploy SQL Agent Jobs.&amp;nbsp; The only practical solution is to manage these through post deployment scripts.&lt;br /&gt;
&lt;br /&gt;
As I've been working on a project that will have a fairly large number of SQL Agent jobs I have come up with a pattern for maintaining these that might be of use to other people.&amp;nbsp; Its a pretty simple approach, it does involve a few manual steps but as you'll see these aren't too bad.&lt;br /&gt;
&lt;br /&gt;
For my projects I like to structure my deployment scripts into a "Scripts" folder.&amp;nbsp; Under that I put a number of sub-folders for various tasks, e.g:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Data population/seeding&lt;/li&gt;
&lt;li&gt;Miscellaneous (usually this just has a script to drop the __Refactor table)&lt;/li&gt;
&lt;li&gt;Jobs&lt;/li&gt;
&lt;/ul&gt;
The "root" Scripts folder just holds a Pre and Post deployment script that will reference other scripts in the various directories.&lt;br /&gt;
&lt;br /&gt;
E.g:&lt;br /&gt;
&lt;pre class="brush: sql"&gt;/*
Post-Deployment Script Template       
--------------------------------------------------------------------------------------
 This file contains SQL statements that will be appended to the build script.  
 Use SQLCMD syntax to include a file in the post-deployment script.   
 Example:      :r .\myfile.sql        
 Use SQLCMD syntax to reference a variable in the post-deployment script.  
 Example:      :setvar TableName MyTable       
               SELECT * FROM [$(TableName)]     
--------------------------------------------------------------------------------------
*/

/* Data Population */
:r ".\Data\Seed Product Categories.sql"

/* Job Categores */
:r ".\Jobs\Job Categories.sql"

/* Jobs */
:r ".\Jobs\Example Job.sql"

/* Refactor table tidy up */
:r ".\Misc\Drop the __Refactor table.sql"

&lt;/pre&gt;
&lt;br /&gt;
This allows me to add and remove scripts quickly and easily.&amp;nbsp; I can even just comment one out if I want.&amp;nbsp; Its also a good way to quickly find deployment code (as long as you name your files sensibly).&lt;br /&gt;
&lt;br /&gt;
In the SSDT project, that'd look something like this:&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;
&lt;/div&gt;
&lt;div class="separator" style="clear: both;"&gt;
&lt;a href="http://2.bp.blogspot.com/-zo42Eqkd594/UF17i5oDCHI/AAAAAAAAAPg/oeHQEma_LGs/s1600/Scripts+folder+screenshot.png.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="336" src="http://2.bp.blogspot.com/-zo42Eqkd594/UF17i5oDCHI/AAAAAAAAAPg/oeHQEma_LGs/s400/Scripts+folder+screenshot.png.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;h3&gt;
The job template script&lt;/h3&gt;
As you'll see from the partial screenshot above there is also a file named "_Template.sql" in the Jobs folder that was not referenced in the PostDeployment.sql file.&amp;nbsp; This template file contains a stub I use for creating the job deployment scripts.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The _Template.sql&amp;nbsp; file contains stubs for various steps we want to go through:&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Define a SQLCMD variable for the job name we want to use.&amp;nbsp; This parameter is used throughout the script as we reference the name a few times.&lt;/li&gt;
&lt;li&gt;Find out if there's an existing job with the same name.&amp;nbsp; If there is we need to determine if we're going to do nothing and leave it alone or if we're going to redeploy the job.&lt;/li&gt;
&lt;li&gt;If there is no existing job with the same name we will deploy the "new" job.&lt;/li&gt;
&lt;li&gt;If there is an existing job we will check another SQLCMD variable RedeployJobs.&amp;nbsp; If that variable is set to "Yes" then we'll drop the existing job and recreate it.&lt;/li&gt;
&lt;li&gt;Depending on results of the above we'll either deploy the job or simply do nothing.&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
It looks like this:&lt;br /&gt;
&lt;pre class="brush: sql"&gt;/*
Job Deployment Script Template                            
--------------------------------------------------------------------------------------

Instructions
------------
    1.  Copy the contents of this script (you do not need to copy these comments/instructions)
        to a script that will contain the job definition.

    2.  Update the JobName parameter with the desired name.

    3.  Script the job from the server using SSMS.

    4.  Copy the job category to the Job Categories.sql post deployment script.

    5.  Copy the relavent sections from the generated script to the various sections below.

    6.  Ensure the database name is correct.  I recommend using the built in $(DatabaseName)
        parameter.

*/

/* Copy from below this line to the end of the script */
--:setvar JobName "Enter the job name within these quotes and uncomment this line"

PRINT 'Deploying job: $(JobName)';
DECLARE @JobId  UNIQUEIDENTIFIER;

SELECT  @JobId = job_id
FROM    msdb.dbo.sysjobs
WHERE   name = '$(JobName)';

/*
 * First we check existence of the specified job name and whether the RedeployJobs parameter
 * has been set to "YES".
 */
IF UPPER('$(RedeployJobs)') = 'YES'   /* Redeploy the job */
OR @JobId IS NULL                   /* It does not exist so deploy it anyway */
BEGIN;
    /*
     * Either this is a new job or the RedeployJobs parameter is set to YES.  If its an existing job
     * we need to remove it so that we can "redeploy" it.
     */
    IF @JobId IS NOT NULL
    BEGIN;
        PRINT ' Deleting existing job';
        EXEC msdb.dbo.sp_delete_job @job_id = @JobId;

        /*
         * Set the @JobId variable to NULL for the sp_add_job command later on.  If it is not null the
         * server things the job is from a MSX server
         */
        SET @JobId = NULL;
    END;
     
    /*
     * Add the job
     */

    /*
     * Add the job step(s)
     */

    /*
     * Add the job schedule
     */

    /*
     * Add the job server
     */
    EXEC msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)';

    PRINT ' Created the job "$(JobName)"';
END;
ELSE
BEGIN;
    PRINT ' Bypassing job "$(JobName)" deployment as job exists and RedeployJob parameter is "$(RedeployJobs)"';
END;
&lt;/pre&gt;
&lt;br /&gt;
Hopefully the template is pretty clear as far as determining whether to deploy the job or not.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Deploying a new job &lt;/h3&gt;
I'm going to assume you've created a job in your development environment that you now want to include in your SSDT project.&amp;nbsp;&amp;nbsp; Here's how we go about that:&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;First we need to create a new "PostDeployment" script in our Jobs folder&lt;/li&gt;
&lt;li&gt;Right click on the Jobs folder --&amp;gt; Add --&amp;gt; Script...&lt;/li&gt;
&lt;li&gt;Select "Post-Deployment Script" and call it the same name as your job (simply so you can find it easily).&amp;nbsp; For the purposes of this blog post, it will be referred to as Example Job.sql&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;
&lt;a href="http://1.bp.blogspot.com/-a-09Z7bdM-U/UF1_wR56TkI/AAAAAAAAAP0/UF_pNLAJMUU/s1600/New+Post+Deployment+Script.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="221" src="http://1.bp.blogspot.com/-a-09Z7bdM-U/UF1_wR56TkI/AAAAAAAAAP0/UF_pNLAJMUU/s320/New+Post+Deployment+Script.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;Click Add&lt;/li&gt;
&lt;li&gt;Open up the _Template.sql file and copy its contents into the new Example Job.sql script.&lt;/li&gt;
&lt;li&gt;In the Example Job.sql script, uncomment the ":setvar JobName ...." line and enter the name of the job between the quotes:&lt;br /&gt;
&lt;pre class="brush:sql"&gt;:setvar JobName "Example Job"&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Now open up SQL Server Management Studio and script out your job to a new query window or file.&amp;nbsp; From here we'll be copying various sections into our new Example Job.sql file to fill in the missing sections.&lt;/li&gt;
&lt;li&gt;First copy the section to add the job:&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;
&lt;a href="http://2.bp.blogspot.com/--2nnhDY5LtU/UF2CuX4KS1I/AAAAAAAAAQI/44mVjQziGYE/s1600/sp_add_job.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="219" src="http://2.bp.blogspot.com/--2nnhDY5LtU/UF2CuX4KS1I/AAAAAAAAAQI/44mVjQziGYE/s320/sp_add_job.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Into the line just below the comment:&lt;br /&gt;
&lt;pre class="brush:sql"&gt;/*
 * Add the job
 */&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Modify the step so that the @job_name parameter uses the SQLCMD variable:&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;
&lt;a href="http://1.bp.blogspot.com/-GGbWO3R6W7g/UF2FpjdDf-I/AAAAAAAAAQk/LtKVXpPIBzI/s1600/sp_add_job+dynamic+dbname.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="166" src="http://1.bp.blogspot.com/-GGbWO3R6W7g/UF2FpjdDf-I/AAAAAAAAAQk/LtKVXpPIBzI/s320/sp_add_job+dynamic+dbname.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;For each job step follow the same approach by copying the sp_add_jobstep commands to the line just below the comment:&lt;br /&gt;
&lt;pre class="brush:sql"&gt;/*
 * Add the job step(s)
 */ &lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Note: &lt;/b&gt;For any step that references the database this project applies to, it is &lt;b&gt;highly &lt;/b&gt;recommended that you update the command to use the SQLCMD variable &lt;pre class="brush:sql"&gt;$(DatabaseName)&lt;/pre&gt;
. E.g.:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;
&lt;a href="http://2.bp.blogspot.com/-V0Ll5MAhia8/UF2FBEM9ptI/AAAAAAAAAQQ/DdpjsDlj3CQ/s1600/DynamicDBName.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="177" src="http://2.bp.blogspot.com/-V0Ll5MAhia8/UF2FBEM9ptI/AAAAAAAAAQQ/DdpjsDlj3CQ/s320/DynamicDBName.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;I also add the "sp_update_job" command in this section.&amp;nbsp; If you prefer you could create your own section for that.&lt;/li&gt;
&lt;li&gt;Finally add the job schedule.  I like to name the schedule the same as the job and therefore use the &lt;code class="brush:sql"&gt;$(JobName)&lt;/code&gt;&lt;br /&gt;
SQLCMD variable here too:&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;
&lt;a href="http://1.bp.blogspot.com/-tibTrKQU-TA/UF2Gy0uuBQI/AAAAAAAAAQs/VeUtpFsUfks/s1600/sp_add_jobschedule.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="159" src="http://1.bp.blogspot.com/-tibTrKQU-TA/UF2Gy0uuBQI/AAAAAAAAAQs/VeUtpFsUfks/s320/sp_add_jobschedule.png" width="320" /&gt;&lt;/a&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;You should now have something that resembles this:&lt;pre class="brush:sql"&gt;:setvar JobName "Example Job"

PRINT 'Deploying job: $(JobName)';
DECLARE @JobId  UNIQUEIDENTIFIER;

SELECT  @JobId = job_id
FROM    msdb.dbo.sysjobs
WHERE   name = '$(JobName)';

/*
 * First we check existence of the specified job name and whether the RedeployJobs parameter
 * has been set to "YES".
 */
IF UPPER('$(RedeployJobs)') = 'YES'   /* Redeploy the job */
OR @JobId IS NULL                   /* It does not exist so deploy it anyway */
BEGIN;
    /*
     * Either this is a new job or the RedeployJobs parameter is set to YES.  If its an existing job
     * we need to remove it so that we can "redeploy" it.
     */
    IF @JobId IS NOT NULL
    BEGIN;
        PRINT ' Deleting existing job';
        EXEC msdb.dbo.sp_delete_job @job_id = @JobId;

        /*
         * Set the @JobId variable to NULL for the sp_add_job command later on.  If it is not null the
         * server things the job is from a MSX server
         */
        SET @JobId = NULL;
    END;
     
    /*
     * Add the job
     */
    EXEC msdb.dbo.sp_add_job @job_name=N'$(JobName)', 
  @enabled=1, 
  @notify_level_eventlog=0, 
  @notify_level_email=0, 
  @notify_level_netsend=0, 
  @notify_level_page=0, 
  @delete_level=0, 
  @description=N'This is a SSDT example job deployment', 
  @category_name=N'Database Maintenance', 
  @owner_login_name=N'sa', @job_id = @jobId OUTPUT;

    /*
     * Add the job step(s)
     */
    EXEC msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Step 1', 
  @step_id=1, 
  @cmdexec_success_code=0, 
  @on_success_action=1, 
  @on_success_step_id=0, 
  @on_fail_action=2, 
  @on_fail_step_id=0, 
  @retry_attempts=0, 
  @retry_interval=0, 
  @os_run_priority=0, @subsystem=N'TSQL', 
  @command=N'SELECT * FROM sys.databases', 
  @database_name=N'$(DatabaseName)', 
  @flags=12;

    EXEC msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1;

    /*
     * Add the job schedule
     */
    EXEC msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'$(JobName)', 
  @enabled=1, 
  @freq_type=4, 
  @freq_interval=1, 
  @freq_subday_type=4, 
  @freq_subday_interval=10, 
  @freq_relative_interval=0, 
  @freq_recurrence_factor=0, 
  @active_start_date=20120922, 
  @active_end_date=99991231, 
  @active_start_time=0, 
  @active_end_time=235959, 
  @schedule_uid=N'64aed10d-3b89-47ae-8f51-ca6d196b1444';

    /*
     * Add the job server
     */
    EXEC msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)';

    PRINT ' Created the job "$(JobName)"';
END;
ELSE
BEGIN;
    PRINT ' Bypassing job "$(JobName)" deployment as job exists and RedeployJob parameter is "$(RedeployJobs)"';
END;
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Save your file&lt;/li&gt;
&lt;li&gt;Update the "master" post deployment script to reference the newly created job script&lt;br /&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
Before you deploy...&lt;/h3&gt;
If you were to try and deploy this now, it would fail.&amp;nbsp; We haven't yet added the $(RedeployJobs) variable to the project.&amp;nbsp; This is very simple to do:&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Open up the properties for the project&lt;/li&gt;
&lt;li&gt;Select the "SQLCMD Variables" tab&lt;/li&gt;
&lt;li&gt;Add the RedeployJobs variable with Yes as the value for Default and Local:&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;
&lt;a href="http://3.bp.blogspot.com/-FgUNRG5zEok/UF2IYNy2bFI/AAAAAAAAAQ0/eCCVCgXoXdM/s1600/RedeployJobs.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="214" src="http://3.bp.blogspot.com/-FgUNRG5zEok/UF2IYNy2bFI/AAAAAAAAAQ0/eCCVCgXoXdM/s320/RedeployJobs.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;h3&gt;
Deploying&lt;/h3&gt;
Now when you deploy you can decide whether to override any existing jobs with the same names or leave them as they are.  As it uses a SQLCMD variable you can specify this as part of your continuous integration tools.  Even if you specify "No" for RedeployJobs a new job will always be deployed.&lt;br /&gt;
&lt;br /&gt;
Whilst there are quite a few steps to follow, once you've done this once or twice you'll realise that its a very easy approach.&amp;nbsp; The main dangers are forgetting to update the generated job names and database references with the appropriate SQLCMD variables. &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=r_thGTY3I0g:qUPIuAKi9so:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=r_thGTY3I0g:qUPIuAKi9so:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/r_thGTY3I0g" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/7302373164119521542/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2012/09/ssdt-sql-agent-job-deployment-template.html#comment-form" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/7302373164119521542?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/7302373164119521542?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/r_thGTY3I0g/ssdt-sql-agent-job-deployment-template.html" title="SSDT SQL Agent Job Deployment Template" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-zo42Eqkd594/UF17i5oDCHI/AAAAAAAAAPg/oeHQEma_LGs/s72-c/Scripts+folder+screenshot.png.jpg" height="72" width="72" /><thr:total>3</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2012/09/ssdt-sql-agent-job-deployment-template.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkYASXg-eSp7ImA9WhJSGE4.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-742621117695697471</id><published>2012-07-09T21:14:00.002+12:00</published><updated>2012-07-09T22:35:48.651+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-07-09T22:35:48.651+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tsql" /><title>Cursors and While loops reply</title><content type="html">Earlier in the year David Curlewis ( &lt;a href="https://twitter.com/databasedave"&gt;t&lt;/a&gt; | &lt;a href="http://www.curlewis.co.nz/"&gt;b&lt;/a&gt; ) wrote a blog post titled &lt;a href="http://www.blogger.com/curlewis.co.nz/2012/03/cursors-and-while-loops/"&gt;"cursors and while loops"&lt;/a&gt;.  If you haven't read that article please go read it now as this is basically a reply or follow-up to that post.&lt;br /&gt;
&lt;br /&gt;
In his article, David showed that for the test he was using the cursor was far and away the higher performing query.  The WHILE loop took 30+ seconds whereas the cursor query took closer to 0.5 seconds.&lt;br /&gt;
&lt;br /&gt;
I spotted a potential improvement to make the WHILE loop behave more in line with what I expected and I think what David expected.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Lets recap&lt;/h2&gt;
Since the hardware I am running on is no doubt different to David's and I ran my tests on SQL Server 2012 with cumulative update 2 (11.0.2325) there are going to be some timing differences.  I believe I have used the same AdventureWorks version of SQL 2008 (get it from &lt;a href="http://msftdbprodsamples.codeplex.com/Releases/"&gt;CodePlex&lt;/a&gt;) and I have used the scripts David used.  &lt;br /&gt;
&lt;br /&gt;
For each test result I have taken the average of 5 tests.&lt;br /&gt;
&lt;br /&gt;
Rather than repeat ALL the cursor tests that David did I've selected the one that performed the fastest: a GLOBAL STATIC CURSOR.  As I said: &lt;a href="http://www.blogger.com/curlewis.co.nz/2012/03/cursors-and-while-loops/"&gt;go read his article&lt;/a&gt; if haven't yet.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
The CURSOR approach&lt;/h3&gt;
&lt;pre class="brush: sql"&gt;------------------------------------------------
-- CURSOR
------------------------------------------------
--DECLARE cursor_name CURSOR [ LOCAL | GLOBAL ]
--     [ FORWARD_ONLY | SCROLL ]
--     [ STATIC | KEYSET | DYNAMIC | FAST_FORWARD ]
--     [ READ_ONLY | SCROLL_LOCKS | OPTIMISTIC ]
--     [ TYPE_WARNING ]
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
GO
SET NOCOUNT ON;
GO
DECLARE @SalesOrderID INT, @LineCount INT;
DECLARE SOID CURSOR GLOBAL STATIC TYPE_WARNING
FOR
    SELECT  SalesOrderID
    FROM    Sales.SalesOrderHeader
    WHERE   OnlineOrderFlag = 1;
 
OPEN SOID;
FETCH NEXT FROM SOID INTO @SalesOrderID;
WHILE @@FETCH_STATUS = 0
BEGIN
    SET @LineCount = ISNULL(@LineCount,0) + ISNULL((
            SELECT  COUNT(*)
            FROM    Sales.SalesOrderDetail
            WHERE   SalesOrderID = @SalesOrderID
            ), 0);
 
    FETCH NEXT FROM SOID INTO @SalesOrderID;
END
CLOSE SOID;
DEALLOCATE SOID;
IF @LineCount &amp;lt;&amp;gt; 60398
BEGIN
    RAISERROR('Bad Total',16,1);
    PRINT @LineCount;
END
GO
&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
When I ran this the code achieved the following metrics:&lt;br /&gt;
&lt;table class="prettyTable"&gt;&lt;thead&gt;
&lt;tr&gt;   &lt;td class="alignRight"&gt;CPU (ms)&lt;/td&gt; &lt;td class="alignRight"&gt;Reads&lt;/td&gt; &lt;td class="alignRight"&gt;Writes&lt;/td&gt; &lt;td class="alignRight"&gt;Duration (ms)&lt;/td&gt;  &lt;/tr&gt;
&lt;/thead&gt; &lt;tbody&gt;
&lt;tr&gt;   &lt;td class="alignRight"&gt;699&lt;/td&gt; &lt;td class="alignRight"&gt;195433&lt;/td&gt; &lt;td class="alignRight"&gt;52&lt;/td&gt; &lt;td class="alignRight"&gt;1216&lt;/td&gt;  &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;
&lt;br /&gt;
&lt;h3&gt;
The WHILE loop&lt;/h3&gt;
&lt;pre class="brush: sql"&gt;------------------------------------------------
-- WHILE LOOP (using counter)
------------------------------------------------
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
GO
SET NOCOUNT ON;
GO
IF OBJECT_ID('tempdb..#SOID') IS NOT NULL DROP TABLE #SOID;
GO
CREATE TABLE #SOID (ID INT IDENTITY, SalesOrderID INT NOT NULL);
DECLARE @i INT = 1, @ii INT, @SalesOrderID INT, @LineCount INT;
 
INSERT  #SOID (SalesOrderID)
SELECT  SalesOrderID
FROM    Sales.SalesOrderHeader
WHERE   OnlineOrderFlag = 1;

SET @ii = @@ROWCOUNT;
 
WHILE @i &amp;lt;= @ii
BEGIN
    SET @SalesOrderID = (SELECT SalesOrderID FROM #SOID WHERE ID = @i);
 
    SET @LineCount = ISNULL(@LineCount, 0) + ISNULL((
            SELECT  COUNT(*)
            FROM    Sales.SalesOrderDetail
            WHERE   SalesOrderID = @SalesOrderID
            ),0);
 
    SET @i += 1;
END
IF @LineCount &amp;lt;&amp;gt; 60398
BEGIN
    RAISERROR('Bad Total',16,1);
    PRINT @LineCount;
END
GO
&lt;/pre&gt;
&lt;br /&gt;
This code achieved the following metrics:&lt;br /&gt;
&lt;table class="prettyTable"&gt;&lt;thead&gt;
&lt;tr&gt;   &lt;td class="alignRight"&gt;CPU (ms)&lt;/td&gt; &lt;td class="alignRight"&gt;Reads&lt;/td&gt; &lt;td class="alignRight"&gt;Writes&lt;/td&gt; &lt;td class="alignRight"&gt;Duration (ms)&lt;/td&gt;  &lt;/tr&gt;
&lt;/thead&gt; &lt;tbody&gt;
&lt;tr&gt;   &lt;td class="alignRight"&gt;39642&lt;/td&gt; &lt;td class="alignRight"&gt;1827358&lt;/td&gt; &lt;td class="alignRight"&gt;60&lt;/td&gt; &lt;td class="alignRight"&gt;41651&lt;/td&gt;  &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;
&lt;br /&gt;
We've gone from 0.7secs to a whopping 39.6secs of CPU and 1.2secs duration to 41.6secs.  Clearly theres an issue with the WHILE loop.&lt;br /&gt;
&lt;br /&gt;
The keen eyed amongst you would have spotted where all that resource is being chewed up - the reads has jumped from 195,433 to 1,827,358.  The WHILE loop is doing nearly 10 times as many reads.&lt;br /&gt;
&lt;br /&gt;
What I realised when I first saw this with David's article was that the ID column lookup on #SQLIO has no index.  Therefore with every iteration of the loop we have to scan that temporary table.  We're really only after one row and we know that the ID is unique because it is an IDENTITY column.  All we need to do is add an index on that column and being unique its quick and easy to make it a primary key...&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
The WHILE loop with a PRIMARY KEY&lt;/h3&gt;
&lt;pre class="brush: sql"&gt;------------------------------------------------
-- WHILE LOOP (using counter)
------------------------------------------------
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
GO
SET NOCOUNT ON;
GO
IF OBJECT_ID('tempdb..#SOID') IS NOT NULL DROP TABLE #SOID;
GO
CREATE TABLE #SOID (ID INT IDENTITY PRIMARY KEY CLUSTERED, SalesOrderID INT NOT NULL);
DECLARE @i INT = 1, @ii INT, @SalesOrderID INT, @LineCount INT;
 
INSERT  #SOID (SalesOrderID)
SELECT  SalesOrderID
FROM    Sales.SalesOrderHeader
WHERE   OnlineOrderFlag = 1;

SET @ii = @@ROWCOUNT;
 
WHILE @i &amp;lt;= @ii
BEGIN
    SET @SalesOrderID = (SELECT SalesOrderID FROM #SOID WHERE ID = @i);
 
    SET @LineCount = ISNULL(@LineCount, 0) + ISNULL((
            SELECT  COUNT(*)
            FROM    Sales.SalesOrderDetail
            WHERE   SalesOrderID = @SalesOrderID
            ),0);
 
    SET @i += 1;
END
IF @LineCount &amp;lt;&amp;gt; 60398
BEGIN
    RAISERROR('Bad Total',16,1);
    PRINT @LineCount;
END
GO
&lt;/pre&gt;
&lt;br /&gt;
And....voila:&lt;br /&gt;
&lt;table class="prettyTable"&gt;&lt;thead&gt;
&lt;tr&gt;   &lt;td class="alignRight"&gt;CPU (ms)&lt;/td&gt; &lt;td class="alignRight"&gt;Reads&lt;/td&gt; &lt;td class="alignRight"&gt;Writes&lt;/td&gt; &lt;td class="alignRight"&gt;Duration (ms)&lt;/td&gt;  &lt;/tr&gt;
&lt;/thead&gt; &lt;tbody&gt;
&lt;tr&gt;   &lt;td class="alignRight"&gt;717&lt;/td&gt; &lt;td class="alignRight"&gt;140336&lt;/td&gt; &lt;td class="alignRight"&gt;31&lt;/td&gt; &lt;td class="alignRight"&gt;1215&lt;/td&gt;  &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;
&lt;br /&gt;
There are a few milliseconds here and there, but the performance of the while loop and cursor are now in line with one another.&lt;br /&gt;
&lt;br /&gt;
Whilst I was here I thought I would try the same query but using a table variable instead.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
The WHILE loop using a TABLE variable&lt;/h3&gt;
Here's the code that I used for this test:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush: sql"&gt;-------------------------------------------------
-- WHILE LOOP (using counter) with TABLE VARIABLE
-------------------------------------------------
CHECKPOINT;
DBCC DROPCLEANBUFFERS;
GO
SET NOCOUNT ON;
GO
DECLARE @SQLIO TABLE(ID INT IDENTITY PRIMARY KEY CLUSTERED, SalesOrderID INT NOT NULL)
DECLARE @i INT = 1, @ii INT, @SalesOrderID INT, @LineCount INT;
 
INSERT  @SQLIO (SalesOrderID)
SELECT  SalesOrderID
FROM    Sales.SalesOrderHeader
WHERE   OnlineOrderFlag = 1;

SET @ii = @@ROWCOUNT;
 
WHILE @i &amp;lt;= @ii
BEGIN
    SET @SalesOrderID = (SELECT SalesOrderID FROM @SQLIO WHERE ID = @i);
 
    SET @LineCount = ISNULL(@LineCount, 0) + ISNULL((
            SELECT  COUNT(*)
            FROM    Sales.SalesOrderDetail
            WHERE   SalesOrderID = @SalesOrderID
            ),0);
 
    SET @i += 1;
END
IF @LineCount &amp;lt;&amp;gt; 60398
BEGIN
    RAISERROR('Bad Total',16,1);
    PRINT @LineCount;
END
GO
&lt;/pre&gt;
&lt;br /&gt;
I fully expected the results for this to be the same as the temporary table.&lt;br /&gt;
&lt;table class="prettyTable"&gt;&lt;thead&gt;
&lt;tr&gt;   &lt;td class="alignRight"&gt;CPU (ms)&lt;/td&gt; &lt;td class="alignRight"&gt;Reads&lt;/td&gt; &lt;td class="alignRight"&gt;Writes&lt;/td&gt; &lt;td class="alignRight"&gt;Duration (ms)&lt;/td&gt;  &lt;/tr&gt;
&lt;/thead&gt; &lt;tbody&gt;
&lt;tr&gt;   &lt;td class="alignRight"&gt;699&lt;/td&gt; &lt;td class="alignRight"&gt;140074&lt;/td&gt; &lt;td class="alignRight"&gt;31&lt;/td&gt; &lt;td class="alignRight"&gt;1183&lt;/td&gt;  &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;
&lt;br /&gt;
And that is basically what we see.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Full results&lt;/h3&gt;
&lt;h4&gt;
CPU&lt;/h4&gt;
&lt;table class="prettyTable"&gt;&lt;thead&gt;
&lt;tr&gt;   &lt;td&gt;Test&lt;/td&gt; &lt;td class="alignRight"&gt;CPU (ms)&lt;/td&gt;&lt;/tr&gt;
&lt;/thead&gt; &lt;tbody&gt;
&lt;tr&gt;   &lt;td&gt;Cursor&lt;/td&gt; &lt;td class="alignRight"&gt;669&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;   &lt;td&gt;While Loop (original)&lt;/td&gt; &lt;td class="alignRight"&gt;39642&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;   &lt;td&gt;While Loop (temp table)&lt;/td&gt; &lt;td class="alignRight"&gt;717&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;   &lt;td&gt;While Loop (temp variable)&lt;/td&gt; &lt;td class="alignRight"&gt;661&lt;/td&gt;  &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;
&lt;br /&gt;
CPU for all tests:&lt;br /&gt;
&lt;a href="http://1.bp.blogspot.com/-bTTB6vEP3Gc/T_qbG8yhY_I/AAAAAAAAAJE/CyspNZyYrE4/s1600/01.+CPU+%28all%29.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="242" src="http://1.bp.blogspot.com/-bTTB6vEP3Gc/T_qbG8yhY_I/AAAAAAAAAJE/CyspNZyYrE4/s400/01.+CPU+%28all%29.png" width="400" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
CPU for tests excluding the original while:&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://2.bp.blogspot.com/-OoQzFH8eivI/T_qbLXiCsYI/AAAAAAAAAJM/yFrJez7XKpc/s1600/01.+CPU+%28excl+original%29.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="243" src="http://2.bp.blogspot.com/-OoQzFH8eivI/T_qbLXiCsYI/AAAAAAAAAJM/yFrJez7XKpc/s400/01.+CPU+%28excl+original%29.png" width="400" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
Duration&lt;/h4&gt;
&lt;table class="prettyTable"&gt;&lt;thead&gt;
&lt;tr&gt;   &lt;td&gt;Test&lt;/td&gt; &lt;td class="alignRight"&gt;Reads&lt;/td&gt;  &lt;/tr&gt;
&lt;/thead&gt; &lt;tbody&gt;
&lt;tr&gt;   &lt;td&gt;Cursor&lt;/td&gt; &lt;td class="alignRight"&gt;1216&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;   &lt;td&gt;While Loop (original)&lt;/td&gt; &lt;td class="alignRight"&gt;41651&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;   &lt;td&gt;While Loop (temp table)&lt;/td&gt; &lt;td class="alignRight"&gt;1215&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;   &lt;td&gt;While Loop (temp variable)&lt;/td&gt; &lt;td class="alignRight"&gt;1184&lt;/td&gt;  &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;
&lt;br /&gt;
Duration for all tests:&lt;br /&gt;
&lt;a href="http://3.bp.blogspot.com/-aqp2yjp4aG0/T_qcg9H3Y2I/AAAAAAAAAJk/EObsN9hqhKk/s1600/02.%2BDuration%2B%2528all%2529.png" imageanchor="1"&gt;&lt;img border="0" height="236" src="http://3.bp.blogspot.com/-aqp2yjp4aG0/T_qcg9H3Y2I/AAAAAAAAAJk/EObsN9hqhKk/s400/02.%2BDuration%2B%2528all%2529.png" width="400" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Duration for tests excluding the original while:&lt;br /&gt;
&lt;a href="http://2.bp.blogspot.com/-hQSvB41P8Jk/T_qcoW803KI/AAAAAAAAAJw/bDYoU9_cgS4/s1600/02.%2BDuration%2B%2528excl%2Boriginal%2529.png" imageanchor="1"&gt;&lt;img border="0" height="236" src="http://2.bp.blogspot.com/-hQSvB41P8Jk/T_qcoW803KI/AAAAAAAAAJw/bDYoU9_cgS4/s400/02.%2BDuration%2B%2528excl%2Boriginal%2529.png" width="400" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
Reads&lt;/h4&gt;
&lt;table class="prettyTable"&gt;&lt;thead&gt;
&lt;tr&gt;   &lt;td&gt;Test&lt;/td&gt; &lt;td class="alignRight"&gt;Reads&lt;/td&gt;  &lt;/tr&gt;
&lt;/thead&gt; &lt;tbody&gt;
&lt;tr&gt;   &lt;td&gt;Cursor&lt;/td&gt; &lt;td class="alignRight"&gt;195433&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;   &lt;td&gt;While Loop (original)&lt;/td&gt; &lt;td class="alignRight"&gt;1827358&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;   &lt;td&gt;While Loop (temp table)&lt;/td&gt; &lt;td class="alignRight"&gt;140336&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;   &lt;td&gt;While Loop (temp variable)&lt;/td&gt; &lt;td class="alignRight"&gt;140074&lt;/td&gt;  &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;
&lt;br /&gt;
Reads for all tests:&lt;br /&gt;
&lt;a href="http://2.bp.blogspot.com/-ncm8YT9expw/T_qcXcPQTZI/AAAAAAAAAJY/zWIvGqNoTLY/s1600/03.%2BReads%2B%2528all%2529.png" imageanchor="1"&gt;&lt;img border="0" height="240" src="http://2.bp.blogspot.com/-ncm8YT9expw/T_qcXcPQTZI/AAAAAAAAAJY/zWIvGqNoTLY/s400/03.%2BReads%2B%2528all%2529.png" width="400" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Reads for tests excluding the original while:&lt;br /&gt;
&lt;a href="http://1.bp.blogspot.com/-WHLCLGjm5uc/T_qdI-IdIkI/AAAAAAAAAJ8/jDyhmPF8ArI/s1600/03.%2BReads%2B%2528excl%2Boriginal%2529.png" imageanchor="1"&gt;&lt;img border="0" height="240" src="http://1.bp.blogspot.com/-WHLCLGjm5uc/T_qdI-IdIkI/AAAAAAAAAJ8/jDyhmPF8ArI/s400/03.%2BReads%2B%2528excl%2Boriginal%2529.png" width="400" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
Writes&lt;/h4&gt;
&lt;table class="prettyTable"&gt;&lt;thead&gt;
&lt;tr&gt;   &lt;td&gt;Test&lt;/td&gt; &lt;td class="alignRight"&gt;Reads&lt;/td&gt;  &lt;/tr&gt;
&lt;/thead&gt; &lt;tbody&gt;
&lt;tr&gt;   &lt;td&gt;Cursor&lt;/td&gt; &lt;td class="alignRight"&gt;52&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;   &lt;td&gt;While Loop (original)&lt;/td&gt; &lt;td class="alignRight"&gt;60&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;   &lt;td&gt;While Loop (temp table)&lt;/td&gt; &lt;td class="alignRight"&gt;31&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;   &lt;td&gt;While Loop (temp variable)&lt;/td&gt; &lt;td class="alignRight"&gt;31&lt;/td&gt;  &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;
&lt;br /&gt;
Writes for all tests:&lt;br /&gt;
&lt;a href="http://3.bp.blogspot.com/-SUhLDgx9mGg/T_qdXRYJL5I/AAAAAAAAAKI/9n2LLGnZKTg/s1600/04.%2BWrites%2B%2528all%2529.png" imageanchor="1"&gt;&lt;img border="0" height="237" src="http://3.bp.blogspot.com/-SUhLDgx9mGg/T_qdXRYJL5I/AAAAAAAAAKI/9n2LLGnZKTg/s400/04.%2BWrites%2B%2528all%2529.png" width="400" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Conclusion&lt;/h3&gt;
I personally don't draw much of a conclusion from these tests.&lt;br /&gt;
&lt;br /&gt;
The cursor and the while loop perform at (more or less) the same level.  It is worth noting that a STATIC CURSOR effectively builds a temporary table in the background.  &lt;a href="http://msdn.microsoft.com/en-us/library/ms180169.aspx"&gt;Books Online about cursors&lt;/a&gt; even has the following:&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;STATIC&lt;br /&gt;
&lt;br /&gt;
Defines a cursor that makes a temporary copy of the data to be used by the cursor. All requests to the cursor are answered from this temporary table in &lt;b&gt;tempdb&lt;/b&gt;; therefore, modifications made to base tables are not reflected in the data returned by fetches made to this cursor, and this cursor does not allow modifications.&lt;/i&gt;&lt;br /&gt;
&lt;br /&gt;
Its therefore unsurprising that performance of these two approaches is on a par with one another.&lt;br /&gt;
&lt;br /&gt;
The table variable approach seems to offer a slight benefit over the others.  However, the figures are very close and this could simply be down to the way the measurements are taken and/or something else running in the background, even if very briefly, at the time of the tests.&lt;br /&gt;
&lt;br /&gt;
Lets be clear about one thing: either of these approaches is a bad solution for the question that it answers.  You can write a very simple set based SELECT query in place of these queries.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=vB-lM2uHY28:UiS7kfZVohw:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=vB-lM2uHY28:UiS7kfZVohw:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/vB-lM2uHY28" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/742621117695697471/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2012/07/cursors-and-while-loops-reply.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/742621117695697471?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/742621117695697471?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/vB-lM2uHY28/cursors-and-while-loops-reply.html" title="Cursors and While loops reply" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-bTTB6vEP3Gc/T_qbG8yhY_I/AAAAAAAAAJE/CyspNZyYrE4/s72-c/01.+CPU+%28all%29.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2012/07/cursors-and-while-loops-reply.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0QFR304fyp7ImA9WhVREUk.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-6860384056164688052</id><published>2012-03-19T21:21:00.001+13:00</published><updated>2012-03-19T21:21:56.337+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-03-19T21:21:56.337+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="DNS" /><category scheme="http://www.blogger.com/atom/ns#" term="CNAMES" /><category scheme="http://www.blogger.com/atom/ns#" term="alias" /><category scheme="http://www.blogger.com/atom/ns#" term="SQLServer" /><title>Using host names? You are doing it wrong!</title><content type="html">This is one of my pet peeves on how many people seem to set up their environment and in particular what they use in their connection strings.&lt;br /&gt;
&lt;br /&gt;
It comes down to this: &lt;b&gt;If you are using the actual host name to connect to your database, you are doing it wrong!&lt;/b&gt;&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
What do I mean?&amp;nbsp; Simple if your database lives on a server named WINSQL01 you should never use that name for your applications.&amp;nbsp; Why?&amp;nbsp; Simple - what if you want to move your database to WINSQL02 through database growth, a fail-over to your disaster recovery server, a hardware refresh or server consolidation?&amp;nbsp; You will now need to reconfigure all your application connections to point the new name (WINSQL02).&amp;nbsp; That is potentially going to require a new application release and that means yet another round of application testing.&lt;br /&gt;
&lt;br /&gt;
From something that could have involved just the DBA (moving the database) we now have to involve the developers or application vendor and your test team.&amp;nbsp; 1 person to potentially dozens.&amp;nbsp; A cheap change to something downright expensive when you add up the time required by all the people.&lt;br /&gt;
&lt;br /&gt;
So how do we avoid this scenario.&amp;nbsp; Its actually quite simple and I'm amazed how few companies are doing this in my experience: You use a DNS CNAME (aka alias).&lt;br /&gt;
&lt;h3&gt;Examples&lt;/h3&gt;Lets say we are using the &lt;code&gt;AdventureWorks&lt;/code&gt; database that resides on WINSQL01.  You would probably have a connection string something like this:&lt;br /&gt;
&lt;pre&gt;Data Source=WINSQL01; Initial Catalog=AdventureWorks; Integrated Security=SSPI;&lt;/pre&gt;&lt;br /&gt;
We might then create a CNAME record similar to this:&lt;br /&gt;
&lt;pre&gt;NAME                  TYPE     VALUE
--------------------- -------- ---------------------
AdventureWorksDB      CNAME    winsql01.contoso.com&lt;/pre&gt;&lt;br /&gt;
We then simply update the connection string to:&lt;br /&gt;
&lt;pre&gt;Data Source=AdventureWorksDB; Initial Catalog=AdventureWorks; Integrated Security=SSPI;&lt;/pre&gt;&lt;br /&gt;
You'll notice that I have simply added "DB" to the end of the database name.  This is a standard you may wish to use, although I prefer another one I will demonstrate shortly.&lt;br /&gt;
&lt;br /&gt;
If we then move our database, for whatever reason, to WINSQL02 we simply update the DNS entry!&lt;br /&gt;
&lt;pre&gt;NAME                  TYPE     VALUE
--------------------- -------- ---------------------
AdventureWorksDB      CNAME    &lt;b&gt;winsql02&lt;/b&gt;.contoso.com&lt;/pre&gt;&lt;br /&gt;
This is almost always a far simpler change to make!&lt;br /&gt;
&lt;br /&gt;
I personally like to set up DNS names for both production and disaster recovery databases.  Especially if I'm using database mirroring.  For that I'd do something like this:&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;NAME                  TYPE     VALUE
--------------------- -------- ---------------------
AdventureWorksProd    CNAME    winsql01.contoso.com
AdventureWorksDR      CNAME    winsql02.contoso.com
&lt;/pre&gt;&lt;br /&gt;
Then the connection string would be:&lt;br /&gt;
&lt;pre&gt;Data Source=&lt;b&gt;AdventureWorksProd&lt;/b&gt;; Failover Partner=&lt;b&gt;AdventureWorksDR&lt;/b&gt;;
Initial Catalog=AdventureWorks; Integrated Security=SSPI;&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;Note:&lt;/b&gt; The connection string wrapping is simply to fit on the screen.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;What about named instances?&lt;/h3&gt;These are a little trickier I will admit.  However; you can still use a DNS CNAME.  Following the examples above, if our AdventureWorks database lived on &lt;code&gt;WINSQL01\SALES&lt;/code&gt; we would use the connection string:&lt;br /&gt;
&lt;pre&gt;Data Source=AdventureWorksDB\SALES; Initial Catalog=AdventureWorks; Integrated Security=SSPI;&lt;/pre&gt;&lt;br /&gt;
The problem comes when moving to another server. If the instance name is the same then you are good to go.  I would try and do this if you have named instances and you want to have DR equivalents.  Everything above still holds true.  If you move the database to a server with another instance name then you have update the connection string. I know of no easy way around that and I would love to hear any suggestions you have.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Arguments against this&lt;/h3&gt;Personally I think there is every reason to do this and absolutely no reason not to do this.  However; here are a few of the "arguments" I've heard against using it:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;It takes an extra network lookup to do this&lt;br /&gt;
&lt;i&gt;WRONG! You have to query DNS to get the IP address for the host name anyway&lt;/i&gt;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Updating the DNS name for a failover takes too long&lt;br /&gt;
&lt;i&gt;Does it take longer than updating the connection strings on all your application servers or users workstations?&amp;nbsp; Why not script it&lt;/i&gt;?&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;I can't remember all the CNAMES we have created&lt;br /&gt;
&lt;i&gt;You could always query the DNS to find out!&amp;nbsp; In a few places I've worked with the DNS admins to export the DNS tables to a text file that we then load into a table and create a Reporting Services report over that.&lt;br /&gt;
&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;I cant (or don't want to) create a CNAME for every database&lt;br /&gt;
&lt;i&gt;You don't have to.&amp;nbsp; Indeed I recommend you create one per application rather than database.&amp;nbsp; Typically an application that uses many databases will need those databases on the same server.&amp;nbsp; Take SharePoint as an example; you often have those DBs on the same server so create a single SharePointDB alias.&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;This really is a no brainer to me, but all too often I see this approach ignored or overlooked.&amp;nbsp; Its such a simple change to the way you work, it costs you nothing but it will save you a huge amount of time and effort when you want to move your databases.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=awpOsTjntD8:HG1tgq0uv4M:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=awpOsTjntD8:HG1tgq0uv4M:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/awpOsTjntD8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/6860384056164688052/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2012/03/using-host-names-you-are-doing-it-wrong.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/6860384056164688052?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/6860384056164688052?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/awpOsTjntD8/using-host-names-you-are-doing-it-wrong.html" title="Using host names? You are doing it wrong!" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2012/03/using-host-names-you-are-doing-it-wrong.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUAMQ345fip7ImA9WhRbF0U.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-7822305493974218906</id><published>2012-02-09T21:04:00.004+13:00</published><updated>2012-02-10T00:43:02.026+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-10T00:43:02.026+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="LazyDBA" /><category scheme="http://www.blogger.com/atom/ns#" term="SQLServer" /><category scheme="http://www.blogger.com/atom/ns#" term="PowerShell" /><title>Listing SQL Server Instances</title><content type="html">As a consultant I go into environments where the client simply does not know how many SQL Server instances they have. Perhaps as a DBA in a company you've been asked to do a license audit and need to know about all the SQL Server instances in your company. Or, as is common in New Zealand so probably elsewhere, you've just started at a company as their first DBA since they've grown to a size that requires one. The same company has probably allowed too many people to have too many rights and all kinds of SQL Server instances have sprung up all over the place.&lt;br /&gt;
&lt;br /&gt;
Its a common situation. You can of course physically go to each machine in your company and check what is installed. But I'm a lazy DBA, or I try to be, and I want a solution that can let me have another coffee before lunch time.&lt;br /&gt;
&lt;br /&gt;
If you do a Bingoogle search for something like "list sql server instances" you'll probably find these articles:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://thepowershellguy.com/blogs/posh/archive/2007/02/09/powershell-get-sqlserverlist.aspx"&gt;http://thepowershellguy.com/blogs/posh/archive/2007/02/09/powershell-get-sqlserverlist.aspx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://sqlvariant.com/wordpress/index.php/2010/09/finding-sql-servers-with-powershell/"&gt;http://sqlvariant.com/wordpress/index.php/2010/09/finding-sql-servers-with-powershell/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
Both of which list this PowerShell command:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:powershell"&gt;[System.Data.Sql.SqlDataSourceEnumerator]::Instance.GetDataSources()&lt;/pre&gt;
&lt;br /&gt;
I did. And when I did all I could think was &lt;i&gt;"Sweet! How easy is that!?"&lt;/i&gt;&lt;br /&gt;
&lt;br /&gt;
Sadly not as easy as it looks. &lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;I ran it and got this result:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;
&lt;a href="http://1.bp.blogspot.com/-JdDP2MaVrks/TzN6_QRyZZI/AAAAAAAAAIA/75rLdEgOJ78/s1600/01.GetDataSourcesResults.jpg" imageanchor="1"&gt;&lt;img border="0" height="455" src="http://1.bp.blogspot.com/-JdDP2MaVrks/TzN6_QRyZZI/AAAAAAAAAIA/75rLdEgOJ78/s640/01.GetDataSourcesResults.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
I honestly cannot explain why that is - but in my "test" environment I also get the same and that's a pretty vanilla setup. But like anything in IT there is always another way. I basically thought "if I can get a list of services on each machine in the domain I'll be able to work out what instances are installed where".&lt;br /&gt;
&lt;br /&gt;
The first half of that equation is easily achieved in PowerShell with the &lt;br /&gt;
&lt;pre class="brush: powershell"&gt;Get-Service -ComputerName HostName&lt;/pre&gt;
command. I just needed a list of computers in the domain to work over. Back to Bingoogle and once again "PowerShell Guy" to the rescue: &lt;a href="http://blogs.technet.com/b/heyscriptingguy/archive/2006/11/09/how-can-i-use-windows-powershell-to-get-a-list-of-all-my-computers.aspx"&gt;http://blogs.technet.com/b/heyscriptingguy/archive/2006/11/09/how-can-i-use-windows-powershell-to-get-a-list-of-all-my-computers.aspx&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
I had a small tweak of the code to pump it into a list that can be saved to a file (computers.txt):&lt;br /&gt;
&lt;pre class="brush:powershell"&gt;$strCategory = "computer"

$objDomain = New-Object System.DirectoryServices.DirectoryEntry

$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.Filter = ("(objectCategory=$strCategory)")

$colProplist = "name"
foreach ($i in $colPropList){
   $res = $objSearcher.PropertiesToLoad.Add($i)
}

$colResults = $objSearcher.FindAll()

$computers = foreach ($objResult in $colResults){
   $objComputer = $objResult.Properties; 
   $objComputer.name
}

$computers

$computers | Out-File "computers.txt"&lt;/pre&gt;
&lt;br /&gt;
Then take that file as the input to the Get-Service cmdlet (you could of course bypass the file...but having a computer list is handy for other tasks...so keep hold of it!):&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush:powershell"&gt;Get-Content "computers.txt" | %{

    Get-Service -ComputerName $_ | ?{$_.Name -like "MSSQLServer" -or $_.Name -like "MSSQL$*"} | Add-Member -MemberType NoteProperty "HostName" -Value $_ -Passthru

} | select HostName, ServiceName, DisplayName, Status&lt;/pre&gt;
&lt;br /&gt;
Which spits out:&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;
&lt;a href="http://3.bp.blogspot.com/-L6cgL1vs2x4/TzN7H6kSb7I/AAAAAAAAAIM/h1Sd_PJiXGQ/s1600/02.UsingGetService.jpg" imageanchor="1"&gt;&lt;img border="0" height="454" src="http://3.bp.blogspot.com/-L6cgL1vs2x4/TzN7H6kSb7I/AAAAAAAAAIM/h1Sd_PJiXGQ/s640/02.UsingGetService.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
And there you have it.  A complete list of SQL Server instances.&lt;br /&gt;
&lt;br /&gt;
There are a couple of things to consider here though.  Firstly you need the ability to query active directory. Secondly you need the ability to query the services on every computer. In larger environments a DBA typically will not be allowed to do that.  You're going to have to go make friends with your domain administrators and either temporarily get some elevated privileges or have them run the code for you.  And thirdly: if you've got servers in a DMZ type environment you may not be aware of those and/or be able to query the services list.  &lt;br /&gt;
&lt;br /&gt;
Also I haven't done too much formatting of the output, but it is possible to turn that into a HOSTNAME\INSTANCE_NAME format. And it would be handy to know the version of SQL Server being run. Which will make a terrific future blog post ;-)&lt;br /&gt;
&lt;br /&gt;
For now - run the scripts and go have your coffee.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=d3YRzCIFMUo:MGzo6aQxsa4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=d3YRzCIFMUo:MGzo6aQxsa4:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/d3YRzCIFMUo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/7822305493974218906/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2012/02/listing-sql-server-instances.html#comment-form" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/7822305493974218906?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/7822305493974218906?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/d3YRzCIFMUo/listing-sql-server-instances.html" title="Listing SQL Server Instances" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-JdDP2MaVrks/TzN6_QRyZZI/AAAAAAAAAIA/75rLdEgOJ78/s72-c/01.GetDataSourcesResults.jpg" height="72" width="72" /><thr:total>4</thr:total><georss:featurename>Auckland, New Zealand</georss:featurename><georss:point>-36.8484597 174.7633315</georss:point><georss:box>-37.2550762 174.1316175 -36.4418432 175.39504549999998</georss:box><feedburner:origLink>http://blog.sqlconcepts.co.nz/2012/02/listing-sql-server-instances.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEYNQncyeSp7ImA9WhRbF0o.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-2482220701413109807</id><published>2012-02-08T00:07:00.002+13:00</published><updated>2012-02-09T21:29:53.991+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-09T21:29:53.991+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Command Line" /><category scheme="http://www.blogger.com/atom/ns#" term="PowerShell" /><title>Deleting files by age with PowerShell</title><content type="html">Back in November 2011 I blogged about using &lt;a href="http://blog.sqlconcepts.co.nz/2011/11/delete-files-by-age-using-forfiles.html"&gt;FORFILES to delete files by age&lt;/a&gt;.&amp;nbsp; I've since been "playing" with PowerShell and intend to blog quite a bit about it over the coming months.&lt;br /&gt;
&lt;br /&gt;
To say PowerShell is powerful is an understatement!&amp;nbsp; There's plenty of material on the Internet to help you too.&amp;nbsp; But I wanted to kick off by replacing that old article with a PowerShell script, basically because I find PowerShell &lt;i&gt;far&lt;/i&gt; easier to read!&lt;br /&gt;
&lt;br /&gt;
But rather than just dumping a command with little explanation, I want to work you through it.  This also demonstrates how I think through a problem with PowerShell and maybe that'll help you too.  So I start with trying to display the items I want to deal with (be it report on, delete, move, action etc):&lt;br /&gt;
&lt;pre class="brush: powershell"&gt;Get-ChildItem "C:\Example"&lt;/pre&gt;
&lt;br /&gt;
That will list all the files in C:\Example directory.  A bit like this:&lt;br /&gt;
&lt;pre&gt;Mode                LastWriteTime     Length Name                                                                       
----                -------------     ------ ----                                                                       
-a---       7/02/2012  11:41 p.m.        598 File A.txt                                                                 
-a---       7/02/2012  11:41 p.m.         95 File B.txt                                                                 
-a---       7/02/2012  11:41 p.m.        311 File C.txt                                                                 
-a---       7/02/2012  11:41 p.m.         12 File D.dat   
&lt;/pre&gt;
&lt;br /&gt;
Note: You can use DIR too as it is aliased to Get-ChildItem.&lt;br /&gt;
&lt;br /&gt;
Ok...but we're interested in files older than an age.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;  So we might come up with something like this:&lt;br /&gt;
&lt;pre class="brush: powershell"&gt;Get-ChildItem "C:\Example" | Where-Object {
   $_.CreationTime -le [datetime]"2012-02-01 00:00:00"
}&lt;/pre&gt;
&lt;br /&gt;
That shows just one file:&lt;br /&gt;
&lt;pre&gt;Mode                LastWriteTime     Length Name                                                                       
----                -------------     ------ ----                                                                       
-a---       7/02/2012  11:41 p.m.        598 File A.txt  
&lt;/pre&gt;
&lt;br /&gt;
But we cant really verify thats the only file older than the date specified.  If you wanted to show the CreationTime you'd need to adjust the command to include a "Select-Object" such as this:&lt;br /&gt;
&lt;pre class="brush: powershell"&gt;Get-ChildItem "C:\Example" | Where-Object {
   $_.CreationTime -le [datetime]"2012-02-01 00:00:00"
} | select Mode, LastWriteTime, CreationTime, Length, Name
&lt;/pre&gt;
That would show:&lt;br /&gt;
&lt;pre&gt;Mode                     LastWriteTime           CreationTime                             Length Name                   
----                     -------------           ------------                             ------ ----                   
-a---                    7/02/2012 11:41:02 p.m. 31/01/2012 7:30:34 a.m.                     598 File A.txt   
&lt;/pre&gt;
&lt;br /&gt;
So to do the delete?  Simple, just pipe into the Remove-Item cmdlet:&lt;br /&gt;
&lt;pre class="brush: powershell"&gt;Get-ChildItem "C:\Example" | Where-Object {
   $_.CreationTime -le [datetime]"2012-02-01 00:00:00"
} | Remove-Item&lt;/pre&gt;
&lt;br /&gt;
And that's pretty much it!&lt;br /&gt;
&lt;br /&gt;
But wait, there's more.  Lets say we wanted to do something like "Show all files older than one month".  We employ the Get-Date cmdlet and one of its methods:&lt;br /&gt;
&lt;pre class="brush: powershell"&gt;Get-ChildItem "C:\Example" | Where-Object {
   $_.CreationTime -le (Get-Date).AddMonths(-1)
}&lt;/pre&gt;
&lt;br /&gt;
Want to know what other methods Get-Date offers?  Try this: &lt;br /&gt;
&lt;pre class="brush: powershell"&gt;Get-Date | Get-Member&lt;/pre&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=wJxFCp8D5G8:RoHps5BHvwY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=wJxFCp8D5G8:RoHps5BHvwY:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/wJxFCp8D5G8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/2482220701413109807/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2012/02/deleting-files-by-age-with-powershell.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/2482220701413109807?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/2482220701413109807?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/wJxFCp8D5G8/deleting-files-by-age-with-powershell.html" title="Deleting files by age with PowerShell" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2012/02/deleting-files-by-age-with-powershell.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEUBQng4fyp7ImA9WhRbF0o.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-8771696880205379845</id><published>2011-12-13T21:11:00.000+13:00</published><updated>2012-02-09T21:30:53.637+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-09T21:30:53.637+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="TSQL Tuesday" /><category scheme="http://www.blogger.com/atom/ns#" term="tsql" /><category scheme="http://www.blogger.com/atom/ns#" term="#tsql2sday" /><title>T-SQL Tuesday #25 - T-SQL Tricks - Selective aggregates</title><content type="html">&lt;a href="http://sqlblog.com/blogs/allen_white/archive/2011/12/05/t-sql-tuesday-025-invitation-to-share-your-tricks.aspx" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://www.upsearch.com/components/com_wordpress/wp/wp-content/uploads/2011/12/tsqltuesday.jpg" /&gt;&lt;/a&gt;&lt;br /&gt;
Firstly, thanks to &lt;a href="http://sqlblog.com/blogs/allen_white/archive/2011/12/05/t-sql-tuesday-025-invitation-to-share-your-tricks.aspx"&gt;Allen White for hosting T-SQL Tuesday #25.&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
This month's T-SQL Tuesday is about T-SQL Tips and Tricks.&amp;nbsp; I mulled over a couple of ideas I had and eventually settled on showing a way to do selective aggregates.&amp;nbsp; Not because I necessarily think its a trick, though it sort of is, but because I keep seeing this done in other terrible ways (as we'll see).&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Selective Aggregates&lt;/h2&gt;
So what is a selective aggregate?&lt;br /&gt;
Well I'm not even sure that's an official name.&amp;nbsp; I just made it up but it does describe the approach.&amp;nbsp; Its best described with an example, and I'm going to use our old friend AdventureWorks (get it from &lt;a href="http://msftdbprodsamples.codeplex.com/"&gt;CodePlex&lt;/a&gt;)&lt;br /&gt;
&lt;br /&gt;
This is a very trivial example, but imagine we wanted to get an aggregate (we're going to use COUNT) of sales by month and year. Well that's quite easy:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush: sql"&gt;select
    year(OrderDate)
    ,month(OrderDate)
    ,count(*)
from
    Sales.SalesOrderHeader
group by
    year(OrderDate)
    ,month(OrderDate)
order by
    year(OrderDate)
    ,month(OrderDate)
&lt;/pre&gt;
&lt;h3&gt;
Results&lt;/h3&gt;
&lt;div class="separator" style="clear: both;"&gt;
&lt;a href="http://1.bp.blogspot.com/-19RHTWHj-gM/TucN8SYmzlI/AAAAAAAAAGQ/psRI4itRPGo/s1600/BasicResults.png" imageanchor="1"&gt;&lt;img border="0" height="320" src="http://1.bp.blogspot.com/-19RHTWHj-gM/TucN8SYmzlI/AAAAAAAAAGQ/psRI4itRPGo/s320/BasicResults.png" width="139" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Boom! And there you have it.&amp;nbsp; But what if we wanted to have years across the top...just like your CFO asked so (s)he could compare year on year?&amp;nbsp; &lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;We could use Pivot for this scenario but thats not what I want to show!&amp;nbsp; If you look at the results we have years 2005 through 2008.&lt;br /&gt;
&lt;br /&gt;
What I'll often see is something like this:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush: sql"&gt;select
    month(soh.OrderDate) as [Month]
    ,(    select count(*) 
          from Sales.SalesOrderHeader as soh2
          where year(soh2.OrderDate) = 2005
          and month(soh.OrderDate) = month(soh2.OrderDate)
    ) as [2005]
    ,(    select count(*) 
          from Sales.SalesOrderHeader as soh2
          where year(soh2.OrderDate) = 2006
          and month(soh.OrderDate) = month(soh2.OrderDate)
    ) as [2006]
    ,(    select count(*) 
          from Sales.SalesOrderHeader as soh2
          where year(soh2.OrderDate) = 2007
          and month(soh.OrderDate) = month(soh2.OrderDate)
     ) as [2007]
     ,(   select count(*) 
          from Sales.SalesOrderHeader as soh2
          where year(soh2.OrderDate) = 2008
          and month(soh.OrderDate) = month(soh2.OrderDate)
     ) as [2008]
from
    Sales.SalesOrderHeader as soh
group by
    month(soh.OrderDate)
order by
    month(soh.OrderDate)
&lt;/pre&gt;
&lt;h3&gt;
Results&lt;/h3&gt;
&lt;div class="separator" style="clear: both;"&gt;
&lt;a href="http://1.bp.blogspot.com/-G3i12bBriz0/TucOhyvkPII/AAAAAAAAAGc/sy0Nmh9e3gI/s1600/CrossTabBadApproach.png" imageanchor="1"&gt;&lt;img border="0" height="302" src="http://1.bp.blogspot.com/-G3i12bBriz0/TucOhyvkPII/AAAAAAAAAGc/sy0Nmh9e3gI/s320/CrossTabBadApproach.png" width="255" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
And it works.  But suddenly we've gone from referencing the Sales.SalesOrderHeader table once to 5 times.  Thats not good.&lt;br /&gt;
&lt;br /&gt;
How else could we achieve the same thing?  The trick is to change your thinking slightly.  We are trying to count occurrences where the year is a given value.  And a count is the same as sum(1).  And thats the trick, we sum the value 1 each time the year is the one we want.  Here's how:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="brush: sql"&gt;select
    month(OrderDate) as [Month]
    ,[2005] = sum(case when Year(OrderDate) = 2005 then 1 else 0 end)
    ,[2006] = sum(case when Year(OrderDate) = 2006 then 1 else 0 end)
    ,[2007] = sum(case when Year(OrderDate) = 2007 then 1 else 0 end)
    ,[2008] = sum(case when Year(OrderDate) = 2008 then 1 else 0 end)
    ,count(*) as TotalInMonth
from
    Sales.SalesOrderHeader
group by
    month(OrderDate)
order by
    month(OrderDate)
&lt;/pre&gt;
&lt;br /&gt;
&lt;h3&gt;
Results&lt;/h3&gt;
&lt;div class="separator" style="clear: both;"&gt;
&lt;a href="http://1.bp.blogspot.com/-e1ySTKX-yZE/TucO7ToO9vI/AAAAAAAAAGo/aI2xz0omwPc/s1600/CrossTabGoodApproach.png" imageanchor="1"&gt;&lt;img border="0" height="301" src="http://1.bp.blogspot.com/-e1ySTKX-yZE/TucO7ToO9vI/AAAAAAAAAGo/aI2xz0omwPc/s320/CrossTabGoodApproach.png" width="250" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Double boom!  As you can see, we wrap a CASE statement inside a SUM.  The case statement simply checks the year value and either reports 1 (count this one) or 0 (don't count this one).  I don't know about you, but that method is much easier to read and to comprehend what is going on.  Not only that its considerably faster.  We are back to referencing the table just the once.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Query Plan Comparison&lt;/h2&gt;
Aside from being easier to read, it has a much improved query plan.&lt;br /&gt;
&lt;br /&gt;
Here is the query plan for the basic select:&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;
&lt;a href="http://4.bp.blogspot.com/-uVjB5nsvzkc/TucQMpKU6SI/AAAAAAAAAG0/dgSgOaBJVJ4/s1600/queryplanbasicselect.png" imageanchor="1"&gt;&lt;img border="0" height="51" src="http://4.bp.blogspot.com/-uVjB5nsvzkc/TucQMpKU6SI/AAAAAAAAAG0/dgSgOaBJVJ4/s320/queryplanbasicselect.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
As you'd probably expect it does a single scan of the table.&lt;br /&gt;
&lt;br /&gt;
Here's the plan for the query with all the sub-selects (brace yourself):&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;
&lt;a href="http://3.bp.blogspot.com/-EALJ6mwrT34/TucQihuryEI/AAAAAAAAAHA/rESzV0rEN_k/s1600/queryplanmultiselect.png" imageanchor="1"&gt;&lt;img border="0" height="100" src="http://3.bp.blogspot.com/-EALJ6mwrT34/TucQihuryEI/AAAAAAAAAHA/rESzV0rEN_k/s320/queryplanmultiselect.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
I had to zoom out a bit to fit it on my screen.  But just glancing at it...it looks worse right!?  That's because it is. Much worse.&lt;br /&gt;
&lt;br /&gt;
And here's the one for the selective aggregate:&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;
&lt;a href="http://2.bp.blogspot.com/-KQEycanztFw/TucQyCkMjBI/AAAAAAAAAHM/K_AD2XNWNQU/s1600/queryplanselectiveaggregate.png" imageanchor="1"&gt;&lt;img border="0" height="42" src="http://2.bp.blogspot.com/-KQEycanztFw/TucQyCkMjBI/AAAAAAAAAHM/K_AD2XNWNQU/s320/queryplanselectiveaggregate.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
That's back to a single table scan.  It actually has 1 less operator than the original select by virtual of 1 less group by.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;
Conclusion&lt;/h2&gt;
In these examples I was counting the records by doing a SUM(1 or 0).  But hopefully you'll have spotted this isn't the only thing you can SUM.  For example, we could have grabbed the "TotalDue" value:&lt;br /&gt;
&lt;pre class="brush: sql"&gt;select
    month(OrderDate) as [Month]
    ,[2005] = sum(case when Year(OrderDate) = 2005 then TotalDue else 0 end)
    ,[2006] = sum(case when Year(OrderDate) = 2006 then TotalDue else 0 end)
    ,[2007] = sum(case when Year(OrderDate) = 2007 then TotalDue else 0 end)
    ,[2008] = sum(case when Year(OrderDate) = 2008 then TotalDue else 0 end)
from
    Sales.SalesOrderHeader
group by
    month(OrderDate)
order by
    month(OrderDate)
&lt;/pre&gt;
&lt;br /&gt;
Which would have yielded these results:&lt;br /&gt;
&lt;div class="separator"&gt;
&lt;a href="http://4.bp.blogspot.com/-G05vFjplIbk/TucSvES2EqI/AAAAAAAAAHY/laz3HffkBXM/s1600/selectiveaggregatestotaldue.png" imageanchor="1"&gt;&lt;img border="0" height="216" src="http://4.bp.blogspot.com/-G05vFjplIbk/TucSvES2EqI/AAAAAAAAAHY/laz3HffkBXM/s320/selectiveaggregatestotaldue.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
There's always many ways to write a query.  And I'm sure many of you will already know this approach which is why I'm not 100% convinced its a T-SQL trick.  But I meet people far too often that simply didn't know you could do this - and I guess that makes it valid for a T-SQL trick.  Try to always think "can I write this a better way" - your DBA will thank you for it.  &lt;br /&gt;
&lt;br /&gt;
I'm now off to &lt;strike&gt;steal&lt;/strike&gt; learn from everyone elses tricks.  I suggest you do too!&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=rBOAGo2H0Jo:YU9-ijx6t1M:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=rBOAGo2H0Jo:YU9-ijx6t1M:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/rBOAGo2H0Jo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/8771696880205379845/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2011/12/t-sql-tuesday-25-t-sql-tricks-selective.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/8771696880205379845?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/8771696880205379845?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/rBOAGo2H0Jo/t-sql-tuesday-25-t-sql-tricks-selective.html" title="T-SQL Tuesday #25 - T-SQL Tricks - Selective aggregates" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-19RHTWHj-gM/TucN8SYmzlI/AAAAAAAAAGQ/psRI4itRPGo/s72-c/BasicResults.png" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2011/12/t-sql-tuesday-25-t-sql-tricks-selective.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkcEQH88cCp7ImA9WhRSGU8.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-8677291998838455955</id><published>2011-11-22T13:00:00.000+13:00</published><updated>2011-11-22T13:00:01.178+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-22T13:00:01.178+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="TSQL tips" /><category scheme="http://www.blogger.com/atom/ns#" term="comments" /><title>5 Steps To Effective And Efficient Comments</title><content type="html">Recently I visited the dentist for a regular trip to the hygienist. My previous hygienist had moved away and so I had a new one. As part of the visit, my new hygienist said something that got me thinking: "I read your notes earlier and from that...". Exactly what followed is unimportant other than to take home the point that my new hygienist already had an idea of my dental history before they even met me.&lt;br /&gt;
&lt;br /&gt;
Those few words struck a chord with me about how important your comments can be in your code. And whilst the example I give describes when someone else is tackling something you've coded, can you honestly remember every line of code you've written? I know I cant and I can think of at least one occasion where I've revisited a piece of code I wrote, had insufficient comments (that would be none) and could not understand why I had done something in a particular way.&lt;br /&gt;
&lt;br /&gt;
So how do we go about ensuring we give good details about the code without wasting time, effort and the concentration of the reader?&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
Here are 5 steps I try to follow when writing code:&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;1. Comment first. Code later&lt;/h3&gt;I'm reminded of a quote from a university lecturer after setting us an assignment:&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;"The earlier you start coding, the further away you are from the solution."&lt;/i&gt;&lt;br /&gt;
&lt;br /&gt;
And he was right.&lt;br /&gt;
&lt;br /&gt;
There's nothing worse than trying to work through someone else's procedure that has no comments. Other than your own code that has no comments.&lt;br /&gt;
&lt;br /&gt;
Rather than commenting your code, take the approach of using your comments to describe the approach you're about to employ and then code around that. I sometimes call this: coding your comments.&lt;br /&gt;
&lt;br /&gt;
This forces you plan out your stored procedure. Collect your thoughts and ensure all the parts of the procedure you require are covered off. This approach gives you a chance to spot errors in your logic before you have even begun coding. These comments don't have to be dozens of lines long, and I'd encourage you to try and keep comments short, but ensure its enough so that you can follow your logic and retain your train of thought.&lt;br /&gt;
&lt;br /&gt;
Think of your comments as pseudo code!&lt;br /&gt;
&lt;br /&gt;
Taking this approach you can achieve the following benefits:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Sufficient comments for you and your colleagues to follow&lt;/li&gt;
&lt;li&gt;You plan your procedure before you start trying to write it&lt;/li&gt;
&lt;li&gt;Potentially spotting issues with your logic before you've even started writing your procedure&lt;/li&gt;
&lt;/ul&gt;Its not a foolproof approach, but I find it saves me quite a bit of time.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;2. Provide a clear description&lt;/h3&gt;Whenever I open up a procedure I want to immediately know what it does. If you have a good description at the top (in your comment block) it'll be much easier to follow the actual code and understand what is going on. Remember that comments are here to make your life easier - give the reader of the code that chance.&lt;br /&gt;
&lt;br /&gt;
If you can give a good description in 1 line that's fine, but on the other hand don't be afraid to take several pages if needed. If I have to write a long description there's a couple of things I try to consider:&lt;br /&gt;
&lt;br /&gt;
Is this procedure too complex? Should I break it down further? Can I provide a reference to a better description (e.g. a document, wiki article or something with a picture)&lt;br /&gt;
&lt;br /&gt;
If you do still have to write a large amount of text I'll even approach the description/comment block as a document by providing a quick synopsis of the function (often with bullet points) then dive into each of the points after they're listed. Doing this gives the reviewer the choice as to how much they read.&lt;br /&gt;
&lt;br /&gt;
E.g: Consider a poorly constructed database where customer details are stored in many tables and not necessarily with a single "Customer" table. You want to write a customer search procedure but each table stores customer details slightly differently.&lt;br /&gt;
&lt;br /&gt;
Here's what I would write as a comment block:&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;Performs a customer search with the name provided. We do this by searching the following tables (in this order):&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;dbo.EmailAliases&lt;/li&gt;
&lt;li&gt;dbo.CustomerOrders&lt;/li&gt;
&lt;li&gt;dbo.CustomerCorrespondence&lt;/li&gt;
&lt;li&gt;dbo.CustomerLeads&lt;/li&gt;
&lt;li&gt;dbo.CustomerArchives&lt;/li&gt;
&lt;/ul&gt;Each table is searched slightly differently, here's how we search each table:&lt;br /&gt;
&lt;br /&gt;
dbo.EmailAliases&lt;br /&gt;
We search this table by comparing the email address supplied. We also try a bit of a pattern search against the names&lt;br /&gt;
&lt;br /&gt;
dbo.CustomerOrders&lt;br /&gt;
We search this table by comparing the delivery address and the customer names&lt;br /&gt;
&lt;br /&gt;
...etc...&lt;/i&gt;&lt;br /&gt;
&lt;h3&gt;3. Only comment as much as you need to&lt;/h3&gt;Does the following code really need a comment?&lt;br /&gt;
&lt;pre class="sql" name="code"&gt;SELECT FirstName, LastName
FROM dbo.Person
WHERE PersonID = @PersonID
&lt;/pre&gt;&lt;br /&gt;
Its pretty clear reading the code what that does. On the other hand, I would argue this code does need a comment:&lt;br /&gt;
&lt;pre class="sql" name="code"&gt;SELECT FirstName, LastName
FROM dbo.Person
WHERE Type = 'X'
&lt;/pre&gt;&lt;br /&gt;
Why is there the "&lt;code&gt;WHERE Type = 'X'&lt;/code&gt;" in the SQL? If you don't tell people why its there how will we know if its correct or not?&lt;br /&gt;
&lt;h3&gt;4. Always comment something ugly, non-standard or just plain weird&lt;/h3&gt;If I'm reviewing a procedure and I see lots of index and optimiser hints I want to know why they're in there.  If you haven't commented a reason to add a NOLOCK hint and I cant see a good reason for it I'm almost certainly going to remove that hint.&lt;br /&gt;
&lt;br /&gt;
Have you applied some business logic to your code?  Why is it in there and what does it do?  If your code follows best practices and you have a well defined, well normalised schema you'll find you dont have to provide many comments at all.  If you're the other 99% of us that don't have that luxury you'll find comments invaluable when you come to fix something later.&lt;br /&gt;
&lt;h3&gt;5. Do not comment out blocks of code&lt;/h3&gt;If its not needed...take it out completely! Don't comment it out "just in case" you need it later.  Use source control rather than commenting out sections.  Thats what source control is for!  Its just as easy to redeploy an old version of a procedure as it is to uncomment a piece of code.  Because its the same function!  I have seen procedures that are comment blocks and no runnable code.  Worse still - there was no comment about why something was commented out!  &lt;br /&gt;
&lt;br /&gt;
If you feel you absolutely must comment out a section of code, at least put a comment alongside it with a date at which point that block can be considered dead and fully removed.  And add a reminder to your calendar!&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;Most technology people hate writing comments, we just want to code and code quickly. Some people even argue your code should be obvious enough to negate the need for any comments. If you can do that then great! But in my experience, and especially with TSQL, its rarely possible to do that. Like my new dental hygienist who hadn't put eyes on my teeth before, and who will almost certainly look at many more sets of teeth before mine again, notes and comments help you get an idea of the task in front of you.  Hopefully one or two of the points will help you quickly write clear and concise comments without taking up much of your time.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=kFeVt03t0mU:HUzJxP7uP4Q:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=kFeVt03t0mU:HUzJxP7uP4Q:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/kFeVt03t0mU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/8677291998838455955/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2011/11/5-steps-to-effective-and-efficient.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/8677291998838455955?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/8677291998838455955?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/kFeVt03t0mU/5-steps-to-effective-and-efficient.html" title="5 Steps To Effective And Efficient Comments" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><thr:total>0</thr:total><georss:featurename>Auckland, New Zealand</georss:featurename><georss:point>-36.8484597 174.7633315</georss:point><georss:box>-37.2550762 174.1316175 -36.4418432 175.39504549999998</georss:box><feedburner:origLink>http://blog.sqlconcepts.co.nz/2011/11/5-steps-to-effective-and-efficient.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEcFSX4-cSp7ImA9WhJSF0w.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-5544538018429629413</id><published>2011-11-08T16:29:00.001+13:00</published><updated>2012-07-08T14:53:38.059+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-07-08T14:53:38.059+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="TSQL Tuesday" /><category scheme="http://www.blogger.com/atom/ns#" term="functions" /><category scheme="http://www.blogger.com/atom/ns#" term="CLR" /><title>T-SQL Tuesday #024 - Calculating Easter with a CLR Function</title><content type="html">&lt;div class="separator" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em; text-align: center;"&gt;&lt;a href="http://bradsruminations.blogspot.com/2011/10/invitation-for-t-sql-tuesday-024-prox-n.html" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_vPa0QtSASSk/TA7I6cjiCrI/AAAAAAAAAYg/Rj8BKsFDao0/s400/tsqltuesday.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;T-SQL Tuesday for November 2011 is about Prox n Funx (Procedures and Functions) and is hosted by Brad Schulz here: &lt;a href="http://bradsruminations.blogspot.com/2011/10/invitation-for-t-sql-tuesday-024-prox-n.html"&gt;http://bradsruminations.blogspot.com/2011/10/invitation-for-t-sql-tuesday-024-prox-n.html&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
This is my first T-SQL Tuesday post (be gentle) and I thought I'd share a function I have recently converted from T-SQL into a CLR based function as it fits with this months topic.&amp;nbsp; I wont be going into what the CLR is or how you go about using it in SQL Server as there are plenty of posts and articles on the Internet already that explain it far better than I ever could.&lt;br /&gt;
&lt;br /&gt;
A little while back I created a blog post on calculating the date of Easter when given a particular year, that post is here: &lt;a href="http://blog.sqlconcepts.co.nz/2011/08/i-like-chocolate-eggswhen-is-it-easter.html"&gt;http://blog.sqlconcepts.co.nz/2011/08/i-like-chocolate-eggswhen-is-it-easter.html&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
The function works fine from a logical point of view, however, due to the type of calculations going on it doesn't perform that well.&amp;nbsp; If you use to populate a date dimension (or other static list of dates) its more than adequate for the job but if you are calling it frequently you'll want something with less overhead and more speed.&lt;br /&gt;
&lt;br /&gt;
For that reason I set about turning it into a C# CLR function.&amp;nbsp; Another reason for doing this was to get me back into the CLR and C# coding as I've only ever done a few pieces here and there.&amp;nbsp; I need the practice!&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
Before I present the C# code and a few instructions to get it into the database a bit of background on calculating the date of Easter.&amp;nbsp; In looking up how to calculate the date from a given year I discovered a few things I hadn't previously considered:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;There is more than one way to calculate the date&lt;/li&gt;
&lt;li&gt;Different parts of the world recognise the date differently&lt;/li&gt;
&lt;/ul&gt;The first item wasn't too much of a surprise, that just became a case of picking the one I was most comfortable with (i.e. the one I could follow and implement!).&amp;nbsp; The second one did come as a bit of a surprise although probably shouldn't have. In the end I went with what is appropriate for New Zealand but if you're in another part of the world you may have to find an alternate method. A chance for a function upgrade in the future perhaps.&lt;br /&gt;
&lt;br /&gt;
The method I eventually went with is called the "Anonymous Gregorian Method".&amp;nbsp; You'll find info on it, and also the algorithm, on Wikipedia: &lt;a href="http://en.wikipedia.org/wiki/Computus#Anonymous_Gregorian_algorithm"&gt;http://en.wikipedia.org/wiki/Computus#Anonymous_Gregorian_algorithm&lt;/a&gt;&lt;br /&gt;
&lt;h3&gt;The Code:&lt;/h3&gt;&lt;pre brush="c#" name="code"&gt;using System;
using System.Data.SqlTypes;

namespace SQLConceptsLtd
{
    public class SqlClrEaster
    {
        [Microsoft.SqlServer.Server.SqlFunction(IsDeterministic = true, IsPrecise = true)]
        public static SqlDateTime CalcEaster(SqlInt32 Year)
        {
            // Function to calculate the date of Easter for a supplied year.
            // If you want to return a wider range of dates to SQL Server you can use
            // the DateTime (.Net) datatype to return to a DateTime2 in SQL Server.  However,
            // that presents other problems notably the inability to set the return
            // value to Null.

            // This function uses the "Anonymous Gregorian algorithm" as described in
            // Wikipedia: http://en.wikipedia.org/wiki/Computus#Anonymous_Gregorian_algorithm


            // Return null if Year is null or out of DateTime range
            if ((Year.IsNull) || (Year &amp;lt; 1753) || (Year &amp;gt; 9999))
                return SqlDateTime.Null;

            // Cast SqlInt32 to int type as we reference it a few times in the calculation
            int year = (int)Year;

            // See the Wikipedia link above to understand the need for all these variables
            int a, b, c, d, e, f, g, h, i, k, l, m, month, day;

            // Here's the calcuation to get the month and day values
            a = year % 19;
            b = year / 100;
            c = year % 100;
            d = b / 4;
            e = b % 4;
            f = ((b + 8) / 25);
            g = ((b - f + 1) / 3);
            h = ((19 * a) + b - d - g + 15) % 30;
            i = c / 4;
            k = c % 4;
            l = (32 + (2 * e) + (2 * i) - h - k) % 7;
            m = (a + (11 * h) + (22 * l)) / 451;

            month = (h + l - (7 * m) + 114) / 31;
            day = ((h + l - (7 * m) + 114) % 31) + 1;

            // return the date!
            return new SqlDateTime(year, month, day);
        }
    }
}
&lt;/pre&gt;There really isn't all that much to this function and if you've looked at the Wikipedia article with its description and algorithm you'll see I've literally just converted it to C# and wrapped a few .Net datatypes around it.&amp;nbsp; I did discover we don't (yet) have support for the SQL DATETIME2 data-type in .Net.&amp;nbsp; I found you could use the .Net DateTime datatype which would then map to the SQL DATETIME2 datatype, however, you cannot set the .Net DateTime datatype to NULL which could be a bit of a problem.&amp;nbsp; For this function I stuck to the SqlDateTime which can be set to NULL.&lt;br /&gt;
&lt;br /&gt;
As I'm still (re)learning C# if I've done something stupid I'd be keen to get some feedback on that so comment away!&lt;br /&gt;
&lt;br /&gt;
Once you compile the class above you'll get a .dll assembly we can load into SQL Server.&amp;nbsp; That's done with the following code:&lt;br /&gt;
&lt;pre class="Sql" name="code"&gt;create assembly CalcEaster
from 'C:\Users\Kent Chenery\Documents\Visual Studio 2010\Projects\SqlClrEaster\SqlClrEaster\bin\Release\SqlClrEaster.dll'
with permission_set = safe;
go

create function dbo.CLRCalcEaster(@Year int)
returns datetime
as
external name CalcEaster.[SQLConceptsLtd.SqlClrEaster].CalcEaster;
go
&lt;/pre&gt;&lt;br /&gt;
I imagine its obvious but you'll have to adjust the assembly source for yourselves and your environment.&lt;br /&gt;
&lt;br /&gt;
You can now call the dbo.CLRCalcEaster function like you would any other scalar function.&amp;nbsp; So to find out the date of Easter in 2012 you simply run:&lt;br /&gt;
&lt;pre class="sql" name="code"&gt;select dbo.CLRCalcEaster(2012);
&lt;/pre&gt;The function will tell you its: 8th April 2012&lt;br /&gt;
&lt;br /&gt;
I said at the beginning of this article that the T-SQL function was slow - thats perhaps not that fair, its not so slow as to be unusable but the CLR equivalent function results in a measurable drop in CPU usage and execution duration.&amp;nbsp; Here's some stats for you to consider:&lt;br /&gt;
&lt;table class="prettyTable"&gt;&lt;thead&gt;
&lt;tr&gt;       &lt;td&gt;# Calculations&lt;/td&gt;       &lt;td&gt;T-SQL Version&lt;/td&gt;       &lt;td&gt;CLR Version&lt;/td&gt;       &lt;td&gt;% Improvement&lt;/td&gt;     &lt;/tr&gt;
&lt;/thead&gt;   &lt;tbody&gt;
&lt;tr&gt;       &lt;td&gt;10000&lt;/td&gt;       &lt;td&gt;0.233&lt;/td&gt;       &lt;td&gt;0.159&lt;/td&gt;       &lt;td&gt;31.8%&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;td&gt;100000&lt;/td&gt;       &lt;td&gt;1.598&lt;/td&gt;       &lt;td&gt;0.998&lt;/td&gt;       &lt;td&gt;37.5%&lt;/td&gt;     &lt;/tr&gt;
&lt;tr&gt;       &lt;td&gt;1000000&lt;/td&gt;       &lt;td&gt;15.445&lt;/td&gt;       &lt;td&gt;8.601&lt;/td&gt;     &lt;td&gt;44.3%&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;br /&gt;
The timings are in seconds and an average of 5 results for each set of tests.&lt;br /&gt;
&lt;br /&gt;
As you can see, with a good number of calculations you get 40% savings in CPU and execution times.  Without any further research/testing my guess for the increased efficiency with more calculations comes from the compilation time becoming a smaller and smaller portion of the overall execution time.&lt;br /&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;This is a trivial example of a CLR function but one that clearly demonstrates the advantages in performance that can be gained with the CLR.&amp;nbsp; I'm planning to explore the CLR further and hopefully post up some more functions and procedures that might be of use to people (other than myself).&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=u-DgJU0Hitw:EHzpSaE6ikA:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=u-DgJU0Hitw:EHzpSaE6ikA:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/u-DgJU0Hitw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/5544538018429629413/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2011/11/t-sql-tuesday-024-calculating-easter.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/5544538018429629413?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/5544538018429629413?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/u-DgJU0Hitw/t-sql-tuesday-024-calculating-easter.html" title="T-SQL Tuesday #024 - Calculating Easter with a CLR Function" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_vPa0QtSASSk/TA7I6cjiCrI/AAAAAAAAAYg/Rj8BKsFDao0/s72-c/tsqltuesday.jpg" height="72" width="72" /><thr:total>0</thr:total><georss:featurename>Auckland, New Zealand</georss:featurename><georss:point>-36.8484597 174.7633315</georss:point><georss:box>-37.2550762 174.1316175 -36.4418432 175.39504549999998</georss:box><feedburner:origLink>http://blog.sqlconcepts.co.nz/2011/11/t-sql-tuesday-024-calculating-easter.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CE4MSH48fyp7ImA9WhRbFkw.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-2976371566271818088</id><published>2011-11-07T09:37:00.001+13:00</published><updated>2012-02-08T00:09:49.077+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-08T00:09:49.077+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Command Line" /><title>Delete files by age using FORFILES</title><content type="html">Ever needed to tidy up some old backups?  Perhaps wanted to delete a bunch of files by age?&lt;br /&gt;
&lt;br /&gt;
If you're using standard SQL tools for local backups and old file tidy ups you can get into the situation I just faced when the files are not tidied up in time.  Your disk fills up, your backup and cleanup task starts failing and the world comes crashing down.&lt;br /&gt;
&lt;br /&gt;
Sure you can manually delete old files, but when there's lots of files in lots of directories and no PowerShell you'll want something to help you.  Enter &lt;a href="http://technet.microsoft.com/en-us/library/cc753551%28WS.10%29.aspx"&gt;FORFILES.EXE&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
I ran the following to tidy up files older than 2 days in the backup directory:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="bat" name="code"&gt;forfiles /m *.* /s /d -2 /c "cmd /c del @path"&lt;/pre&gt;&lt;br /&gt;
That command will delete (&lt;code&gt;/c "cmd /c del @path"&lt;/code&gt;) all files (&lt;code&gt;/m *.*&lt;/code&gt;) older than 2 days (&lt;code&gt;/d -2&lt;/code&gt;) in the current folder and all its sub-directories (&lt;code&gt;/s&lt;/code&gt;).&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Update (2012-02-07)&lt;/h3&gt;I recommend you check out PowerShell for all your command line needs.  I blogged about a PowerShell way to do this here: &lt;a href="http://blog.sqlconcepts.co.nz/2012/02/deleting-files-by-age-with-powershell.html"&gt;http://blog.sqlconcepts.co.nz/2012/02/deleting-files-by-age-with-powershell.html&lt;/a&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=uZ1L8kvincA:Q3Q25CVQT5Q:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=uZ1L8kvincA:Q3Q25CVQT5Q:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/uZ1L8kvincA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/2976371566271818088/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2011/11/delete-files-by-age-using-forfiles.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/2976371566271818088?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/2976371566271818088?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/uZ1L8kvincA/delete-files-by-age-using-forfiles.html" title="Delete files by age using FORFILES" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2011/11/delete-files-by-age-using-forfiles.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C08HQXw7cCp7ImA9WhRRFk0.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-6331755105439994808</id><published>2011-09-19T20:58:00.001+12:00</published><updated>2011-11-30T10:23:50.208+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-30T10:23:50.208+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="1433" /><category scheme="http://www.blogger.com/atom/ns#" term="telnet" /><category scheme="http://www.blogger.com/atom/ns#" term="Firewall" /><category scheme="http://www.blogger.com/atom/ns#" term="1434" /><category scheme="http://www.blogger.com/atom/ns#" term="port" /><title>SQL Server and the Windows Firewall</title><content type="html">Anyone who's been administering SQL Server for more than 10 minutes knows that SQL Server uses port 1433 (by default) for connections.  Once you been administering SQL Server for a bit longer you learn that the following default ports and protocols are used by the following services:&lt;br /&gt;
&lt;br /&gt;
&lt;table class="prettyTable"&gt;&lt;thead&gt;
&lt;tr&gt;&lt;td&gt;Port&lt;/td&gt;&lt;td&gt;Protocol&lt;/td&gt;&lt;td&gt;Used By&lt;/td&gt;&lt;/tr&gt;
&lt;/thead&gt; &lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1433&lt;/td&gt;&lt;td&gt;TCP&lt;/td&gt;&lt;td&gt;Default port for SQL Server connections&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1434&lt;/td&gt;&lt;td&gt;UDP&lt;/td&gt;&lt;td&gt;Port for the SQL Browser service.  In essence, the browser service returns a list of instances and the ports they are listening on that reside on the host.  It is mostly used to identify where named instances are listening&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5022&lt;/td&gt;&lt;td&gt;TCP&lt;/td&gt;&lt;td&gt;The default port for Mirroring&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;
&lt;br /&gt;
&lt;b&gt;Note:&lt;/b&gt; As far as I know; other than port 1434 for the Browser the ports can all be changed.  Feel free to correct me!&lt;br /&gt;
&lt;br /&gt;
So what?  Well, if you are running in any kind of secure environment (we all keep our databases secure don't we?) then you'll need to be poking pinholes through the odd firewall here and there.  For example; you may have a web server out in a DMZ but retain the database server in your "corporate" LAN.  That is quite a common set up, and as such you have to open up connections to your SQL Server through a firewall.&lt;br /&gt;
&lt;h3&gt;
Testing if the ports are opened&lt;/h3&gt;
By far the easiest way to do this is to follow these steps:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://technet.microsoft.com/en-us/library/cc771275%28WS.10%29.aspx"&gt;Install the TELNET client&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Open a COMMAND PROMPT&lt;/li&gt;
&lt;li&gt;Run the following: &lt;code&gt;TELNET [HOST IP ADDRESS] [PORT NUMBER]&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
E.G: &lt;code&gt;TELNET 192.168.1.1 1433&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
If the port is open you'll generally be presented with a blank screen (helpful huh).  However; if it fails you'll see the following:&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;
&lt;a href="http://1.bp.blogspot.com/-Wwq4C4PaBg4/Tnb_0keCj2I/AAAAAAAAAE0/OTFYs1bP5w4/s1600/telnet%2Berror.jpg" imageanchor="1"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-Wwq4C4PaBg4/Tnb_0keCj2I/AAAAAAAAAE0/OTFYs1bP5w4/s1600/telnet%2Berror.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;h3&gt;
Opening the ports on your server&lt;/h3&gt;
You can of course manually edit the firewall rules, but running the script (or portions of the script) below is &lt;b&gt;much quicker&lt;/b&gt; and far easier...&lt;br /&gt;
&lt;br /&gt;
This quick little script came out of recently building up a virtual environment to test some database mirroring.  I could have been lazy and simply disabled the Windows firewall but thought I'd see if I could whip up a script.  Here's what came out:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="txt" name="code"&gt;REM Enable ICMPv4 (aka PING)
netsh advfirewall firewall set rule name="File and Printer Sharing (Echo Request - ICMPv4-IN)" dir=in new enable=yes
netsh advfirewall firewall set rule name="File and Printer Sharing (Echo Request - ICMPv4-Out)" dir=out new enable=yes

REM Add rules for SQL Server inbound
netsh advfirewall firewall add rule name="SQL Server (Engine - 1433)" dir=in action=allow protocol=TCP localport=1433
netsh advfirewall firewall add rule name="SQL Server (Browser - 1434)" dir=in action=allow protocol=UDP localport=1434
netsh advfirewall firewall add rule name="SQL Server (Mirroring - 5022)" dir=in action=allow protocol=TCP localport=5022


REM Add rules for SQL Server outbound
netsh advfirewall firewall add rule name="SQL Server (Engine - 1433)" dir=out action=allow protocol=TCP remoteport=1433
netsh advfirewall firewall add rule name="SQL Server (Browser - 1434)" dir=out action=allow protocol=UDP remoteport=1434
netsh advfirewall firewall add rule name="SQL Server (Mirroring - 5022)" dir=out action=allow protocol=TCP remoteport=5022
&lt;/pre&gt;
&lt;br /&gt;
&lt;i&gt;Sorry about the spill over...I need to find a better template&lt;/i&gt;&lt;br /&gt;
&lt;br /&gt;
As you'll also see, its quite easy to define other rules and/or amend the port numbers in use.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Update (2011-11-30)&lt;/h3&gt;
The best resource I've found for the list of port numbers required for the SQL Server range of services is this article in Books Online &lt;a href="http://technet.microsoft.com/en-us/library/cc646023.aspx"&gt;http://technet.microsoft.com/en-us/library/cc646023.aspx&lt;/a&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=Nhto14HtWF4:AvjgiHxMgsQ:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=Nhto14HtWF4:AvjgiHxMgsQ:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/Nhto14HtWF4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/6331755105439994808/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2011/09/sql-server-and-windows-firewall.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/6331755105439994808?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/6331755105439994808?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/Nhto14HtWF4/sql-server-and-windows-firewall.html" title="SQL Server and the Windows Firewall" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-Wwq4C4PaBg4/Tnb_0keCj2I/AAAAAAAAAE0/OTFYs1bP5w4/s72-c/telnet%2Berror.jpg" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2011/09/sql-server-and-windows-firewall.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A04NQ34yfSp7ImA9WhdWGU0.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-9047197768407838411</id><published>2011-09-13T22:33:00.002+12:00</published><updated>2011-09-13T22:33:12.095+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-09-13T22:33:12.095+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="TSQL tips" /><category scheme="http://www.blogger.com/atom/ns#" term="tsql" /><category scheme="http://www.blogger.com/atom/ns#" term="templates" /><title>Effective TSQL Templates</title><content type="html">&lt;h3&gt;What are TSQL Templates?&lt;/h3&gt;If you haven't discovered TSQL templates yet, you've been missing out on an extremely useful feature of SQL Server Management Studio (SSMS).  We'll get into why they're so good very shortly but lets start by opening up the Template Explorer so you can see what they are; click View → Template Explorer:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;&lt;a href="http://1.bp.blogspot.com/-bvQRhDCC0-Y/TmSsHgZY8SI/AAAAAAAAAD8/oOB6BzE3M50/s1600/01.OpenTemplateExplorer.png" imageanchor="1"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-bvQRhDCC0-Y/TmSsHgZY8SI/AAAAAAAAAD8/oOB6BzE3M50/s1600/01.OpenTemplateExplorer.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Or if you're like me and you like your keyboard shortcuts: &lt;code&gt;Ctrl + Alt + T&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Though this shortcut is strangely missing in SSMS in Denali CTP3.  This will open up the Template Explorer that has dozens of TSQL templates to build from. Go ahead and explore them.&lt;br /&gt;
&lt;h3&gt;Template Replacements&lt;/h3&gt;Using the "Alter Stored Procedure Template" as an example (its under the Stored Procedure folder of the Template Explorer), you will see many of the templates have some strange looking code amongst them. It looks tenuously like XML in that the bits of code appear to be tags surrounded by &lt;code&gt;&amp;lt;&lt;/code&gt; and &lt;code&gt;&amp;gt;&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;&lt;a href="http://3.bp.blogspot.com/-_MRSBESY1y0/TmSwxCNST5I/AAAAAAAAAEI/lGUxiO1QvDA/s1600/02.TemplateReplacements.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-_MRSBESY1y0/TmSwxCNST5I/AAAAAAAAAEI/lGUxiO1QvDA/s1600/02.TemplateReplacements.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
What are these exactly?&lt;br /&gt;
&lt;br /&gt;
Well, they're a handy way to allow you specify replacement values in your template. More commonly known as template parameters. With the "Alter Stored Procedure Template" you are always going to need to specify the procedure you want to modify. The fully qualified name takes the form schema.object_name, in the example above I have highlighted the piece of code that allows for a common replacement for a schema name.  You might be thinking "that looks awfully complicated for something that could be specified simply with &amp;lt;schema&amp;gt;". There's a reason for this; we have the ability to specify both a data-type and a default value for the replacement.&lt;br /&gt;
&lt;br /&gt;
The full "syntax" for the template parameters is:&lt;br /&gt;
&lt;code&gt;&lt;b style="color: red;"&gt;&amp;lt;&lt;/b&gt;Parameter_Name&lt;b style="color: red;"&gt;, &lt;/b&gt;Data_Type&lt;b style="color: red;"&gt;, &lt;/b&gt;Default_Value&lt;b style="color: red;"&gt;&amp;gt;&lt;/b&gt;&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Whilst you have the ability to specify a data type, this actually plays no meaning (as far as I can tell). It simply helps &lt;b&gt;you&lt;/b&gt; identify the kind of data you are expecting to replace here.  You can even leave the data type and default value sections empty if you like.&lt;br /&gt;
&lt;h3&gt;Setting Template Parameter Values&lt;/h3&gt;How do we go about actually using these template parameters?  Click Query → Specify Values For Template Parameters:&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;&lt;a href="http://4.bp.blogspot.com/-lmKVG888yI8/TmS1y_cuymI/AAAAAAAAAEQ/gtRLOJ2NFFY/s1600/03.TemplateReplacementValues.png" imageanchor="1"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-lmKVG888yI8/TmS1y_cuymI/AAAAAAAAAEQ/gtRLOJ2NFFY/s1600/03.TemplateReplacementValues.png" /&gt;&amp;nbsp;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Or use the keyboard shortcut &lt;code&gt;Ctrl + Shift + M&lt;/code&gt;&lt;br /&gt;
Yet another missing shortcut from Denali.&lt;br /&gt;
&lt;br /&gt;
When you initiate the "Specify Values for Template Parameters" you'll receive the following dialog box:&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;&lt;a href="http://3.bp.blogspot.com/-tbElYGLa2kU/TmS2srneF0I/AAAAAAAAAEY/H347LTV9U0E/s1600/04.TemplateReplacementValuesDialog.png" imageanchor="1"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-tbElYGLa2kU/TmS2srneF0I/AAAAAAAAAEY/H347LTV9U0E/s1600/04.TemplateReplacementValuesDialog.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
If you enter some values and click ok, you'll find the values replaced in your template:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;&lt;a href="http://4.bp.blogspot.com/-AIA66GEpyIc/TmS31BChMGI/AAAAAAAAAEg/UAjEQoI_kAY/s1600/05.ValuesReplaced.png" imageanchor="1"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-AIA66GEpyIc/TmS31BChMGI/AAAAAAAAAEg/UAjEQoI_kAY/s1600/05.ValuesReplaced.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;h3&gt;Modifying the default templates&lt;/h3&gt;After you've experimented with the default templates, you'll soon discover that you want to:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Modify existing templates - either to add your own spin to them or adhere to your coding standards&lt;/li&gt;
&lt;li&gt;Add templates of your own&lt;/li&gt;
&lt;/ul&gt;If you right click on one of the templates, rather than double clicking, you'll see an "edit" option:&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;&lt;a href="http://3.bp.blogspot.com/-E8nkJxnLhBw/Tm8lPIzdbYI/AAAAAAAAAEk/bzJlxEA6NdA/s1600/template+edit.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-E8nkJxnLhBw/Tm8lPIzdbYI/AAAAAAAAAEk/bzJlxEA6NdA/s1600/template+edit.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Selecting edit will open the template, as per earlier, and allow you to make modifications to it. Lets add a comment block and save the changes:&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;&lt;a href="http://1.bp.blogspot.com/-HUGSlORea8E/Tm8mKjQZ3dI/AAAAAAAAAEo/TKrwYdNAv5o/s1600/template+edit+add+description.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-HUGSlORea8E/Tm8mKjQZ3dI/AAAAAAAAAEo/TKrwYdNAv5o/s1600/template+edit+add+description.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Save your changes.&lt;br /&gt;
&lt;br /&gt;
Now reopen your template.&lt;br /&gt;
&lt;br /&gt;
This is all great.&amp;nbsp; Right up until you edit any of the "... (New Menu)" templates.&amp;nbsp; For example; if you edit the "Create Procedure (New Menu)" template, save and reopen you'll find everything looks as if it should.&amp;nbsp; And perhaps you did this following my instructions above.&amp;nbsp; But now open a database, navigate to the Stored Procedures in that database, right click and select "New Stored Procedure":&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;&lt;a href="http://1.bp.blogspot.com/-YbUcg0tA16c/Tm8oryUncqI/AAAAAAAAAEs/OBsEhmAUw8k/s1600/New+Stored+Procedure.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-YbUcg0tA16c/Tm8oryUncqI/AAAAAAAAAEs/OBsEhmAUw8k/s1600/New+Stored+Procedure.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
You'll find the original "...(New Menu)" template is opened.&amp;nbsp; None of your changes have propagated through.&amp;nbsp; Why is this?&lt;br /&gt;
&lt;br /&gt;
Well the answer is quite simple, and arguably a bug but we'll settle for it being labelled as a feature.&lt;br /&gt;
&lt;br /&gt;
When you edit any of the templates you edit the templates stored in: &lt;code&gt;%APPDATA%\Microsoft\Microsoft SQL Server\100\Tools\Shell\Templates\Sql&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
This seems to be the path used when you open a template through the template explorer too. Unfortunately, when you use the "New Menu" options through SSMS it doesn't use your profile copy, but the original install copy found in: &lt;code&gt;C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\SqlWorkbenchProjectItems\Sql&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
To me; this a little bit crazy and a hugely counter intuitive.  But at least we can "fix" this by updating these versions. T-SQL templates are a useful feature, especially if you want to standardise a few areas such as comment blocks.  But as you'll have seen from the array of pre-supplied templates you can use them to build up an array of shortcuts for building your code. These are further extended in Denali, or by 3rd party tools including SSMS Tools (which is free), by the use of code snippets.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;An effective "New Menu" template&lt;/h3&gt;Now that you know where to update the templates to affect the "New ..." option from SSMS its up to you to go and create your effective templates.  But to help you, here's one I use for my coding that I find useful:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="sql" name="code"&gt;--use &amp;lt;DBName, sysname, &amp;gt;;
--go

if not exists(select 1 from sys.objects where object_id = object_id('[&amp;lt;Schema, sysname, dbo&amp;gt;].[&amp;lt;Object Name, sysname,&amp;gt;]')
begin;
	exec sp_executeSQL N'create procedure [&amp;lt;Schema, sysname, dbo&amp;gt;].[&amp;lt;Object Name, sysname,&amp;gt;] as select ''Procedure Stub'';';
end;
go

alter procedure [&amp;lt;Schema, sysname, dbo&amp;gt;].[&amp;lt;Object Name, sysname,&amp;gt;]
/***********************************************
 * Procedure:	[&amp;lt;Schema, sysname, dbo&amp;gt;].[&amp;lt;Object Name, sysname,&amp;gt;]
 * Date:		&amp;lt;Created Date, datetime, &amp;gt;
 * Author:		&amp;lt;Created By, ,Your Name&amp;gt;
 *
 * Description:	
 *
 * ------------ --------------- ----------------------------------------------------
 * Date			Author			Description
 * ------------ --------------- ----------------------------------------------------
 * &amp;lt;Created Date, datetime, &amp;gt;	&amp;lt;Created By, ,Your Name&amp;gt;	
 * ------------ --------------- ----------------------------------------------------
 */
as
/*
 * Procedure Options
 */
set nocount on;

/*
 * Start transaction
 */
begin transaction &amp;lt;Schema, sysname, dbo&amp;gt;_&amp;lt;Object Name, sysname,&amp;gt;;

/*
 * Do the work
 */

/*
 * Commit transaction
 */
commit transaction &amp;lt;Schema, sysname, dbo&amp;gt;_&amp;lt;Object Name, sysname,&amp;gt;;

/*
 * End of procedure
 */
go;
&lt;/pre&gt;&lt;br /&gt;
I recommend you amend the "New Menu" templates at the very least as these are the easiest ones to use.  And they're likely to be the ones you reference the most if not the only ones you reference.  What you define as your template is up to you, but do think about what you want to always include. Its a good opportunity to encourage comments or at the very least comment blocks in stored procedures, functions and views.  I'll be writing a blog post about effective commenting in the coming weeks and setting up some templates will help in this area.&lt;br /&gt;
&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=Ml1Mc6A57qQ:AzpsDbFgeto:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=Ml1Mc6A57qQ:AzpsDbFgeto:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/Ml1Mc6A57qQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/9047197768407838411/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2011/09/effective-tsql-templates.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/9047197768407838411?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/9047197768407838411?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/Ml1Mc6A57qQ/effective-tsql-templates.html" title="Effective TSQL Templates" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-bvQRhDCC0-Y/TmSsHgZY8SI/AAAAAAAAAD8/oOB6BzE3M50/s72-c/01.OpenTemplateExplorer.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2011/09/effective-tsql-templates.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUcARns7eSp7ImA9WhdXFkw.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-2019086075972063963</id><published>2011-08-29T22:16:00.001+12:00</published><updated>2011-08-29T22:17:27.501+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-08-29T22:17:27.501+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="presentations" /><category scheme="http://www.blogger.com/atom/ns#" term="full text search" /><category scheme="http://www.blogger.com/atom/ns#" term="code camp" /><title>Discovering Full Text Search</title><content type="html">And so Code Camp 2011 comes to a close for another year.  I can honestly say I had a great time and enjoyed presenting my topic: Discovering Full Text Search&lt;br /&gt;
&lt;br /&gt;
Apologies for not being able to get through all the demos, I'm a bit disappointed about that as there were a couple I think were of great value.  One in particular around the thesaurus as there were a couple of questions I think it would have answered quite well.&lt;br /&gt;
&lt;br /&gt;
However, this then becomes a great opportunity to present the topic again at the &lt;a href="http://www.aucklandsql.com/"&gt;Auckland SQL Users Group&lt;/a&gt;.  There were quite a few people from Auckland who admitted to having not attended the users group before - it would be good to see some of you.  We have free pizza and beer!&lt;br /&gt;
&lt;h3&gt;The goodness&lt;/h3&gt;Until then, you're welcome to &lt;a href="http://goo.gl/3VC2m"&gt;view/download the slides to the presentation&lt;/a&gt;.&lt;br /&gt;
And &lt;a href="http://goo.gl/d9Wef"&gt;grab the code for the demos&lt;/a&gt; - even the ones I skipped.  You will need the AdventureWorks2008R2 database which you can download from &lt;a href="http://msftdbprodsamples.codeplex.com/"&gt;http://msftdbprodsamples.codeplex.com/&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
If you did attend, thanks for coming and I hope you enjoyed it and managed to get something out of it.&lt;br /&gt;
&lt;h3&gt;Feedback&lt;/h3&gt;I'd love some feedback. I'm still fairly new to presenting so have a long way to go.  What did you like, what didn't you like? Could you hear me OK, did I mumble? etc. Feel free to leave a comment or perhaps more ideally drop me a email (see my &lt;a href="http://blog.sqlconcepts.co.nz/p/contact.html"&gt;contact&lt;/a&gt; page above).&lt;br /&gt;
&lt;h3&gt;Shameless Plug&lt;/h3&gt;We've got SQL Saturday coming up. There's going to be fireworks! Literally.  Its on the 5th of November. Put it in your diaries. More info will be available on the Auckland SQL website (&lt;a href="http://www.aucklandsql.com/SQL-Saturday.aspx"&gt;http://www.aucklandsql.com/SQL-Saturday.aspx&lt;/a&gt;) when it becomes available.&lt;br /&gt;
&lt;br /&gt;
If you did not hang around for Leo Millers excellent security presentation you missed a couple of things:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;a very good presentation with lots to think about&lt;/li&gt;
&lt;li&gt;and my little nugget (found the probable original): &lt;a href="http://stuff.creativityhurts.net/post/9468362554/best-sql-injection-out-there-translation-for"&gt;http://stuff.creativityhurts.net/post/9468362554/best-sql-injection-out-there-translation-for&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Until next year...&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=-Yp4AW0oN0U:9aEcBqOcgXg:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=-Yp4AW0oN0U:9aEcBqOcgXg:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/-Yp4AW0oN0U" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/2019086075972063963/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2011/08/discovering-full-text-search.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/2019086075972063963?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/2019086075972063963?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/-Yp4AW0oN0U/discovering-full-text-search.html" title="Discovering Full Text Search" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2011/08/discovering-full-text-search.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C04ER384eyp7ImA9WhdQE0Q.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-707757842749258422</id><published>2011-08-13T22:41:00.006+12:00</published><updated>2011-08-15T19:05:06.133+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-08-15T19:05:06.133+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="training" /><category scheme="http://www.blogger.com/atom/ns#" term="code camp" /><title>We dont need no education!</title><content type="html">Way back in 1979 Pink Floyd argued "&lt;a href="http://www.youtube.com/watch?v=t4SKL7f9n58"&gt;we don't need no education!&lt;/a&gt;". But that was over 30 years ago (sheesh...just how old am I?!) and education is fast becoming a scarce commodity. In just &lt;i&gt;under&lt;/i&gt; two weeks TechEd NZ 2011 kicks off.&lt;br /&gt;
&lt;br /&gt;
But I'm not here to tell you about that. In just &lt;i&gt;over&lt;/i&gt; two weeks a far more cost accessible set of training is available: &lt;b&gt;&lt;a href="http://www.codecamp.co.nz/"&gt;Code Camp&lt;/a&gt;&lt;/b&gt;.&lt;br /&gt;
&lt;br /&gt;
I've been to a couple of code camps and the first one I attended, I think, was 2008 at Whitireia in Porirua. And I'm going to be brutally honest: it was better than TechEd. I kid you not. For a SQL Server professional this was a seriously good event. The quality of presentations and knowledge was amazing I met some great people and best of all it was free!&lt;br /&gt;
&lt;h3&gt;"You! Yes you laddie!"&lt;/h3&gt;On the 28th of August we have &lt;b&gt;Code Camp 2011&lt;/b&gt; in Auckland.&lt;br /&gt;
&lt;br /&gt;
Personally I'm interested in the SQL Server track, but we also have:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Development Track - you know...all things .Net&lt;/li&gt;
&lt;li&gt;SharePoint Track - Im guessing...but I think this focuses on SharePoint&lt;/li&gt;
&lt;li&gt;Infastructure Track - shared with SQL track to cover AD, SSO, NLB and other awesome TLAs&lt;/li&gt;
&lt;li&gt;Business Track - a new one covering topics that don't sit comfortably numb elsewhere.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Check out the full agenda for all the sessions here: &lt;a href="http://www.blogger.com/Check%20out%20all%20the%20sessions%20here:%20http://www.dot.net.nz/codecamp/Pages/AllSessions.aspx%20"&gt;http://www.dot.net.nz/codecamp/Pages/AllSessions.aspx &lt;/a&gt; &lt;br /&gt;
&lt;br /&gt;
But lets be honest with ourselves...we're only interested in the SQL Server track so what are we covering here?&amp;nbsp; Since the SQL track and the Infrastructure track are shared, the first two sessions are for Infrastructure topics, then we get into it:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;11:30 - Full Text Search&lt;/li&gt;
&lt;li&gt;12:30 - Lunch. Quite possibly the most important session.&amp;nbsp; Seriously it is - this is your chance to quiz everyone!&lt;/li&gt;
&lt;li&gt;13:00 - Monitoring multiple SQL instances for $0.00.&amp;nbsp; I'm personally looking forward to this one&lt;/li&gt;
&lt;li&gt;14:00 - Minimal Logging and Data Manoeuvring on Very Large Tables&lt;/li&gt;
&lt;li&gt;15:00 - Afternoon Tea - Yummy!&lt;/li&gt;
&lt;li&gt;15:30 - SQL Server: How Secure are you?&lt;/li&gt;
&lt;li&gt;16:30 - Close and Prizes - Yet more "free stuff" (we do love free at Code Camp)&lt;/li&gt;
&lt;/ul&gt;&lt;h3&gt;"If you dont eat your meat, you cant have any pudding.  How can you have any pudding if you dont eat your meat?"&lt;/h3&gt;Going to TechEd NZ 2011? Then have your pudding too and come to Code Camp!&lt;br /&gt;
Not going to TechEd NZ 2011? I'll let you in on a secret: you can still have your pudding.  It is free after all!&lt;br /&gt;
&lt;br /&gt;
Come along to put another brick in your (education) wall. With a price like that, just why wouldn't you go? &lt;br /&gt;
&lt;br /&gt;
Want some more info? Your two best sources are the Code Camp website (&lt;a href="http://www.codecamp.co.nz/"&gt;http://www.codecamp.co.nz/&lt;/a&gt;) and the &lt;a href="https://twitter.com/#%21/search/%23ccnz"&gt;#ccnz&lt;/a&gt; hash tag on twitter.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Teacher. Leave them kids alone.&lt;/h3&gt;And yes, I'm speaking! Please come say "Hello".&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=khkxpxx7peU:JbXcWRGXBFc:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=khkxpxx7peU:JbXcWRGXBFc:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/khkxpxx7peU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/707757842749258422/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2011/08/we-dont-need-no-education.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/707757842749258422?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/707757842749258422?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/khkxpxx7peU/we-dont-need-no-education.html" title="We dont need no education!" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2011/08/we-dont-need-no-education.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkcERn48fCp7ImA9WhdRGE4.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-4270908863589334381</id><published>2011-08-09T07:00:00.000+12:00</published><updated>2011-08-09T07:00:07.074+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-08-09T07:00:07.074+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tsql" /><category scheme="http://www.blogger.com/atom/ns#" term="identity" /><title>Heading towards an identity crisis?</title><content type="html">My client recently struck an issue with an identity column value. Basically they ran out! This brought a very visible (read customer facing) interruption to one of their systems.&lt;br /&gt;
&lt;br /&gt;
I'm sure many of you are in the situation where you have defined an identity column for your primary key. There's nothing particularly wrong with that but do you keep an eye on whether you're near to running out of values or not?&lt;br /&gt;
&lt;br /&gt;
Simply run this script to find out:&lt;br /&gt;
&lt;pre class="Sql" name="code"&gt;with cteIdentity as (
    -- Basic identity info
    select 
    o.object_id                                  as TableID
    ,c.column_id                                 as ColumnID
    ,s.name + '.' + o.name                       as TableName
    ,ident_current(s.name + '.' + o.name)        as CurrentIdentity
    ,power(2.0, ((8 * c.max_length) - 1)) - 1    as MaxIdentity
    from
        sys.objects as o
        inner join sys.columns as c
            on o.object_id = c.object_id
        inner join sys.schemas as s
            on o.schema_id = s.schema_id
        where
            c.is_identity = 1    -- Table has an identity column
        and o.type = 'U'         -- User tables only
)
-- Calc the percentage used
select
    TableName
    ,CurrentIdentity
    ,(CurrentIdentity / MaxIdentity) * 100       as PercentageOfMax
from
    cteIdentity;
&lt;/pre&gt;&lt;br /&gt;
It would be very easy to wrap some alerts around the results if the "PercentageOfMax" value is above a predefined threshold level (e.g: 95%)&lt;br /&gt;
&lt;br /&gt;
Naturally this does not fix the issue you might be screaming towards, but it gives you that important "heads up" that you need to start planning for a change soon.&lt;br /&gt;
&lt;br /&gt;
Consider that to increase the data type to bigint from an int (which is what you would typically have to do) could involve:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;An outage to perform the table change &lt;/li&gt;
&lt;li&gt;The need to re-factor several (many?) procedures that reference the table&lt;/li&gt;
&lt;li&gt;Since its likely the identity is a primary key you're going to have to amend any tables that reference this one&lt;/li&gt;
&lt;li&gt;You'll need to release an application update to cater for the increase in data-type size&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Trust me; its far easier to address this before it happens than during an identity crisis.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=YFftjFUxzH8:yjWDiIPv8Y4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=YFftjFUxzH8:yjWDiIPv8Y4:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/YFftjFUxzH8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/4270908863589334381/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2011/08/heading-towards-identity-crisis.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/4270908863589334381?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/4270908863589334381?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/YFftjFUxzH8/heading-towards-identity-crisis.html" title="Heading towards an identity crisis?" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2011/08/heading-towards-identity-crisis.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A08HRXwzcSp7ImA9WhdRFEk.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-4214792843927207619</id><published>2011-08-04T21:22:00.001+12:00</published><updated>2011-08-04T21:23:54.289+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-08-04T21:23:54.289+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tsql" /><category scheme="http://www.blogger.com/atom/ns#" term="Easter" /><category scheme="http://www.blogger.com/atom/ns#" term="date" /><category scheme="http://www.blogger.com/atom/ns#" term="functions" /><title>I like chocolate eggs...when is it Easter again?</title><content type="html">A little while back I was working on a new data warehouse and was building the date dimension.  The date dimension needed to cater for some seasonality adjustments and as such needed to know when specific holidays occurred.  Most holidays are pretty easy to work out:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Christmas is usually on the 25th of Dec&lt;/li&gt;
&lt;li&gt;New Year is round about the 1st of Jan&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Some holidays are not so clear; Easter moves all over the place!&amp;nbsp; That makes populating a date dimension a little awkward, but at least once you've done it you don't have to worry about it again.&lt;br /&gt;
&lt;br /&gt;
I found a couple of sites that listed when Easter had or would occur (date dimensions need to cater for dates in the past and potentially in the future).&amp;nbsp; If you Google for Easter dates you'll get them too.&amp;nbsp; But I really wanted a programmatic way to find the date for a given year.&lt;br /&gt;
&lt;br /&gt;
With a little bit of hunting I found this: &lt;a href="http://www.bbc.co.uk/dna/h2g2/A653267"&gt;http://www.bbc.co.uk/dna/h2g2/A653267&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Good on ya BBC!&lt;br /&gt;
&lt;br /&gt;
A bit of jiggery-pokery later I give you (I hope they don't mind me regurgitating it):&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="Sql" name="code"&gt;create function [dbo].[CalcEaster](@Year int)
returns datetime2
with schemabinding
as
begin
 /*
  * This function originally came from http://www.bbc.co.uk/dna/h2g2/A653267 which read:
  *
  * Given the Year as a four digit number in the range 1700 - 2299
  * Calculate Day and Month of Easter Sunday
  *  Note 1: the algorithm has not been tested outside this range.
  *  Note 2: the \ operator performs integer division without remainder.
  *  Note 3: the date returned is the Gregorian Calendar date
  *          (the one we use now), even for dates in the 18th Century.
  * 
  * a = Year mod 19;
  * b = Year \ 100;
  * c = Year mod 100;
  * d = b \ 4;
  * e = b mod 4;
  * f = c \ 4;
  * g = c mod 4;
  * 
  * h = (b + 8)\25;
  * i = (b - h + 1)\3;
  * j = (19*a + b - d - i + 15) mod 30;
  * k = (32 + 2*e + 2*f - j - g) mod 7;
  * m = (a + 11*j + 22*k) \ 451;
  * n = j + k - 7*m + 114;
  * 
  * Month = n\31;
  * Day = (n mod 31) + 1;
  */
 declare @a  int
 declare @b  int
 declare @c  int
 declare @d  int
 declare @e  int
 declare @f  int
 declare @g  int
 declare @h  int
 declare @i  int
 declare @j  int
 declare @k  int
 declare @l  int
 declare @m  int
 declare @n  int

 select @a = (@Year % 19)
 select @b = (@Year / 100)
 select @c = (@Year % 100)
 select @d = ((@Year / 100) / 4)
 select @e = ((@Year / 100) % 4)
 select @f = ((@Year % 100) / 4)
 select @g = ((@Year % 100) % 4)

 select @h = (@b + 8) / 25
 select @i = (@b - @h +1 ) / 3
 select @j = ((19 * @a) + @b - @d - @i + 15) % 30
 select @k = (32 + (2 * @e) + (2 * @f) - @j - @g) % 7
 select @m = (@a + (11 * @j) + (22 * @k)) / 451
 select @n = (@j + @k) - (7 * @m) + 114

 /*
  * Return the values as a datetime
  */
 return convert(datetime2, convert(char(4), @Year) + '-' + right('0' + convert(varchar(2), (@n / 31)), 2) + '-' + right('0' + convert(varchar(2), (@n % 31) + 1), 2))
end
&lt;/pre&gt;&lt;br /&gt;
So when is Easter 2012?&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both;"&gt;&lt;a href="http://4.bp.blogspot.com/-NYFok83aWI4/TjpketmEMFI/AAAAAAAAADk/0wauX003ILM/s1600/CalcEaster2012.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-NYFok83aWI4/TjpketmEMFI/AAAAAAAAADk/0wauX003ILM/s1600/CalcEaster2012.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
Enjoy.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=KqCNuxRdiWg:KmOqspuumj0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=KqCNuxRdiWg:KmOqspuumj0:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/KqCNuxRdiWg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/4214792843927207619/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2011/08/i-like-chocolate-eggswhen-is-it-easter.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/4214792843927207619?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/4214792843927207619?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/KqCNuxRdiWg/i-like-chocolate-eggswhen-is-it-easter.html" title="I like chocolate eggs...when is it Easter again?" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-NYFok83aWI4/TjpketmEMFI/AAAAAAAAADk/0wauX003ILM/s72-c/CalcEaster2012.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2011/08/i-like-chocolate-eggswhen-is-it-easter.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck8FRHk8fyp7ImA9WhdSF0s.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-6934894520724340697</id><published>2011-07-27T22:00:00.000+12:00</published><updated>2011-07-27T22:00:15.777+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-27T22:00:15.777+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="sequences" /><title>Sequences for the pre-Denali generation</title><content type="html">One of the raved about new features of SQL Server Denali is Sequences. And why not? They will be a fantastic, if long overdue, feature in my opinion.&lt;br /&gt;
&lt;br /&gt;
There has been the odd time in my career I could have done with them. Indeed I recently had cause to make use of them, however, they're simply not available yet. Sometimes an identity will do, but sometimes you need a &lt;b&gt;pseudo sequence&lt;/b&gt;!&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;h3&gt;So what are sequences?&lt;/h3&gt;Before we dive into how to generate my so called "pseudo sequences", it is best to cover what they are. If you've used identity columns in the past you're about 80% of the way to understanding them. Essentially they are a counter much like an identity column. Unlike the identity property of a column, a sequence is not tied to a particular column - indeed it is not necessarily tied to a column at all.  And unlike an identity column, you must explicitly increment the sequence.&lt;br /&gt;
&lt;h3&gt;Is the identity property dead?&lt;/h3&gt;Most definitely not! Like any tool, sequences have their place but they certainly do not solve every scenario. You are more likely to use an identity column than a sequence value, but as I have already mentioned there are times when an identity column simply wont do.&lt;br /&gt;
&lt;h3&gt;When would you use a sequence?&lt;/h3&gt;A couple of scenarios immediately spring to mind:&lt;br /&gt;
&lt;h4&gt;Scenario 1 : Manual table partitioning&lt;/h4&gt;You might have two (or more) tables that have the same auto generated key that you do not want to overlap. A scenario for this might be where you have manually partitioned tables based on a function. I recently had such a scenario: records could be generated from two sources; a customer and a system generated record. The customer records would not need validating, but the system ones would. Normally you'd create something like an IsValidated flag column - but modifying the existing schema was not possible (though I was allowed to create new tables). So I created a holding table where records could be checked before being pushed into the real table. Overlaps of keys wasn't allowed and it was required that in either scenario the key value was returned to the customer/system. A sequence would be great...but we're not working in SQL Server 2011 yet.&lt;br /&gt;
&lt;h4&gt;Scenario 2 : Incident management systems&lt;/h4&gt;Another scenario I've encountered is with ticketing or problem management systems. The "ticket" table would have an identity column for the primary key and this value would be used to store the ticket/incident number the customer would reference. Unfortunately you cannot give the customer their reference number until the ticket is saved. The work around most systems take is to create a blank record to display the ticket number and then update once the details are collected - this ends up with lots of orphan entries in your DB. Again a sequence is ideal: take the next sequence number and do an insert once all the data is collected.&lt;br /&gt;
&lt;h3&gt;I'm convinced...I want them NOW!&lt;/h3&gt;Firstly, we need a table to store our sequence values:&lt;br /&gt;
&lt;pre class="Sql" name="code"&gt;/*
 * Sequence Table
 */
if object_id('dbo.SequenceStore') is not null
begin;
 drop table dbo.SequenceStore;
end;
go

create table dbo.SequenceStore(
 SequenceStoreID   int identity(1, 1) not null
 ,SequenceName   varchar(50)   not null
 ,Value     bigint    null
 ,StartValue    bigint    not null
 ,MinValue    bigint    not null
 ,MaxValue    bigint    not null
 ,Increment    bigint    not null
 ,DateCreated   datetime2   not null
 ,DateLastAccessed  datetime2   null
 ,DateLastIncremented datetime2   null
);
go
/*
 * Primary key
 */
alter table dbo.SequenceStore
add constraint PK_SequenceStore
primary key clustered(SequenceStoreID)
with fillfactor = 100;
go

/*
 * Unique constraint on the name
 */
alter table dbo.SequenceStore
add constraint UC_SequenceStore_SequenceName
unique(SequenceName)
with fillfactor = 90;
go

/*
 * Defaults for the SequenceStore
 */
alter table dbo.SequenceStore
add constraint DF_SequenceStore_MinValue
default(0)
for MinValue;
go

alter table dbo.SequenceStore
add constraint DF_SequenceStore_MaxValue
default(9223372036854775807)
for MaxValue;
go

alter table dbo.SequenceStore
add constraint DF_SequenceStore_Increment
default(1)
for Increment;
go

alter table dbo.SequenceStore
add constraint DF_SequenceStore_DateCreated
default(sysdatetime())
for DateCreated;
go
&lt;/pre&gt;&lt;br /&gt;
A way to create sequences within our environment:&lt;br /&gt;
&lt;pre class="Sql" name="code"&gt;/*
 * dbo.SequenceCreate
 */
if object_id('dbo.SequenceCreate') is not null
begin;
 drop procedure dbo.SequenceCreate;
end;
go

create procedure dbo.SequenceCreate
 @SequenceName   varchar(50)     /* Name to assign to the sequence */
 ,@MinValue    bigint  = null   /* Min value of the sequence */
 ,@MaxValue    bigint  = null   /* Max value of the sequence */
 ,@Increment    bigint  = null   /* Increment value for the sequence */
 ,@StartValue   bigint  = null    /* A starting value */
 ,@NewSequenceID   int   = null output /* The new ID created */
as
/*
 * Procedure Options
 */
set nocount on;  /* Provide a slight performance improvement */

/*
 * Verify optional input paramenter values
 */
select
 @MinValue  = isnull(@MinValue, 0)
 ,@MaxValue  = isnull(@MaxValue, 9223372036854775807)
 ,@Increment  = isnull(@Increment, 1)
 ,@StartValue = isnull(@StartValue, @MinValue)
 ;

/*
 * Check the name doesn't already exist
 */
if exists(select 1 from dbo.SequenceStore where SequenceName = @SequenceName)
begin;
 raiserror('Unable to create new sequence as the name "%s" already exists', 16, 1, @SequenceName);
 return -1;
end;

/*
 * Check the StartValue, if supplied, is between the min and max
 */
if @StartValue is not null and not(@StartValue between @MinValue and @MaxValue)
begin;
 /*
  * Cant create the sequence as the start value is outside the bounds of the min/max values
  */
 raiserror('Unable to create the new sequence as the Start Value (%I64d) is outside of the Min (%I64d) and Max (%I64d) values', 16, 1, @StartValue, @MinValue, @MaxValue);
 return -2;
end;

/*
 * Add the record
 */
begin tran txSequenceCreate;

insert into dbo.SequenceStore(
 SequenceName
 ,Value
 ,StartValue
 ,MinValue
 ,MaxValue
 ,Increment
 )
values(
 @SequenceName
 ,null
 ,@StartValue
 ,@MinValue
 ,@MaxValue
 ,@Increment
 );

/*
 * Get the sequence ID value
 */
select @NewSequenceID = scope_identity();

commit tran txSequenceCreate;
go
&lt;/pre&gt;&lt;br /&gt;
A way to increment our sequence (safely):&lt;br /&gt;
&lt;pre class="Sql" name="code"&gt;/*
 * dbo.SequenceIncrement
 */
if object_id('dbo.SequenceIncrement') is not null
begin;
 drop procedure dbo.SequenceIncrement;
end;
go

create procedure dbo.SequenceIncrement
 @SequenceName   varchar(50)     /* Name of the sequence */
 ,@NumToGen    int  = 1     /* Number of sequences to generate */
 ,@Value     bigint = null output  /* The value after the increment */
as
/*
 * Procedure Options
 */
set nocount on;  /* Provide a slight performance improvement */

/*
 * Variables
 */
declare @NewValue bigint;
declare @MinValue bigint;
declare @MaxValue bigint;

/*
 * Verify the sequence exists
 */
if not exists(select 1 from dbo.SequenceStore where SequenceName = @SequenceName)
begin;
 raiserror('No sequence named "%s" exists', 16, 1, @SequenceName);
 return -1;
end;

/*
 * Increment the sequence
 */
begin transaction txSequenceIncrement;

/*
 * Get the next value with an update lock to ensure no other processes can
 * increment at the same time.
 */
select
 @NewValue = case
     when Increment &amp;gt; 0 then isnull(Value + (Increment * @NumToGen), (Increment * (@NumToGen - 1)) + MinValue)
     /* Less than zero, so need to use MaxValue if we starting the sequence from scratch (null) */
     else isnull(Value + (Increment * @NumToGen), (Increment * (@NumToGen - 1)) + MaxValue)
    end
 ,@MinValue = MinValue
 ,@MaxValue = MaxValue
from
 dbo.SequenceStore
 with(updlock, rowlock) /* row and update lock hints */
where
 SequenceName = @SequenceName;

/*
 * Verify the value is between the Min and Max - that is we haven't
 * exceeded the allowed range
 */
if @NewValue not between @MinValue and @MaxValue
begin;
 /*
  * Cannot do the increment as we'll exceed the range allowed
  */
 raiserror('Unable to increment the sequence as the allowable range will be exceeded', 16, 1);
 rollback transaction txSequenceIncrement;

 /*
  * Exit
  */
 return -2;
end;
else
begin;
 /*
  * Update the current row
  */
 update dbo.SequenceStore
 set
  Value     = @NewValue
  ,DateLastAccessed  = sysdatetime()
  ,DateLastIncremented = sysdatetime()
 where
  SequenceName = @SequenceName;

 /*
  * Set the output variable
  */
 set @Value = @NewValue;
end;

commit transaction txSequenceIncrement;
go
&lt;/pre&gt;&lt;br /&gt;
And for good measure; a way to reset our sequence:&lt;br /&gt;
&lt;pre class="Sql" name="code"&gt;/*
 * dbo.SequenceReset
 */
if object_id('dbo.SequenceReset') is not null
begin;
 drop procedure dbo.SequenceReset;
end;
go

create procedure dbo.SequenceReset
 @SequenceName   varchar(50)     /* Name of the sequence */
as
/*
 * Procedure Options
 */
set nocount on;  /* Provide a slight performance improvement */

/*
 * Variables
 */

/*
 * Verify the sequence exists
 */
if not exists(select 1 from dbo.SequenceStore where SequenceName = @SequenceName)
begin;
 raiserror('No sequence named "%s" exists', 16, 1, @SequenceName);
 return -1;
end;

begin transaction txSequenceReset;

update dbo.SequenceStore
set
 Value = null
where
 SequenceName = @SequenceName;

commit transaction txSequenceReset;
/*
 * End of procedure
 */
go
&lt;/pre&gt;There are other procedures or functions you could create, for example a "peek" procedure that returned the next value but did not increment might be useful.&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;But how do I use that stuff?&lt;/h4&gt;&lt;ol&gt;&lt;li&gt;Run the code to create the objects.&lt;/li&gt;
&lt;li&gt;Use the procedure dbo.SequenceCreate to add sequences to your environment. You must give them a name, but all the other parameters are optional. You can specify min and max range values and increment amounts.&lt;/li&gt;
&lt;li&gt; Every time you need a new sequence value call dbo.SequenceIncrement. Specify the name and capture the new value in the output parameter&lt;/li&gt;
&lt;/ol&gt;The eagle eyed amongst you will notice a couple of optimiser hints in the dbo.SequenceIncrement procedure.  These are to ensure only one increment "thread" can happen at a time on a given sequence.  Without them we could hit some serious data integrity problems as two threads could increment to the same value!&lt;br /&gt;
&lt;h4&gt;"Hold on...&lt;/h4&gt;&lt;i&gt;...You've used SQL Server 2008 features like DATETIME2 and I'm stuck on SQL 2005 (or even SQL 2000)"&lt;/i&gt;&lt;br /&gt;
This is true, but the code would be extremely easy to adjust for earlier versions. I can't do your whole job for you. You can, however, hire me for that!&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Drawbacks&lt;/h3&gt;As it uses procedures you cannot auto apply a value to a column. And alas, as we need to update a table we cannot use a function to do this - unless someone can come up with a clever hack.&lt;br /&gt;
&lt;br /&gt;
Its awkward to load into a table with this approach in a set based way. You need to prefetch all your sequences, therefore you need to know how many records are in play. You can then use a variable and/or nifty function to set the value. Something like: &lt;br /&gt;
&lt;pre&gt;MinOfRangeGathered + (SequenceIncrement * (ROW_NUMBER - 1))&lt;/pre&gt;&lt;br /&gt;
The table can become a "hotspot" in your database if you're incrementing your sequence frequently. There is also the potential for a heavy amount of contention on the records in the table. Having said that; testing on my laptop yielded more than adequate results. I was able to increment 20,000 times across two threads in a couple of seconds - &lt;a href="http://www.urbandictionary.com/define.php?term=YMMV"&gt;YMMV&lt;/a&gt; (and it almost certainly will).&lt;br /&gt;
&lt;h2&gt;Disclaimer&lt;/h2&gt;I provide absolutely no warranty on this code.  You are free to use, modify and disseminate as you wish. Please give credit where its due.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=SZJNvK3CxGE:KP5isgPXxMg:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=SZJNvK3CxGE:KP5isgPXxMg:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/SZJNvK3CxGE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/6934894520724340697/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2011/07/sequences-for-pre-denali-generation.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/6934894520724340697?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/6934894520724340697?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/SZJNvK3CxGE/sequences-for-pre-denali-generation.html" title="Sequences for the pre-Denali generation" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2011/07/sequences-for-pre-denali-generation.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUYASX45fip7ImA9WhdTGUU.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-2049074689301433795</id><published>2011-07-18T21:46:00.001+12:00</published><updated>2011-07-18T21:59:08.026+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-18T21:59:08.026+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tsql" /><category scheme="http://www.blogger.com/atom/ns#" term="functions" /><title>Who Am I?</title><content type="html">SQL Server has no less than 13 functions to capture the current "person" running a piece of code. (And I may have missed some too!)&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;USER&lt;/li&gt;
&lt;li&gt;CURRENT_USER&lt;/li&gt;
&lt;li&gt;SESSION_USER&lt;/li&gt;
&lt;li&gt;SYSTEM_USER&lt;/li&gt;
&lt;li&gt;USER_NAME()&lt;/li&gt;
&lt;li&gt;USER_ID()&lt;/li&gt;
&lt;li&gt;USER_SID()&lt;/li&gt;
&lt;li&gt;SUSER_NAME()&lt;/li&gt;
&lt;li&gt;SUSER_SNAME()&lt;/li&gt;
&lt;li&gt;SUSER_ID()&lt;/li&gt;
&lt;li&gt;SUSER_SID()&lt;/li&gt;
&lt;li&gt;ORIGINAL_LOGIN()&lt;/li&gt;
&lt;li&gt;DATABASE_PRINCIPAL_ID() &lt;/li&gt;
&lt;/ol&gt;Why so many? What do they all do? Why do some end in parentheses and some do not? And, more importantly, which one do I really need?&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
For something as simple as finding out who is calling a piece of T-SQL, there sure do seem to be a lot of functions!&amp;nbsp; Lets take a look at what each ones does; paraphrasing from Books OnLine...&lt;br /&gt;
&lt;h4&gt;USER&lt;/h4&gt;The USER function returns the name of the currently executing context.&amp;nbsp; If the context is switched with an EXECUTE AS the impersonated context is used.&lt;br /&gt;
&lt;br /&gt;
This function returns the current database user.&lt;br /&gt;
&lt;br /&gt;
Return type: &lt;b&gt;CHAR &lt;/b&gt;&lt;br /&gt;
&lt;h4&gt;CURRENT_USER&lt;/h4&gt;CURRENT_USER returns the name of the current security context.&amp;nbsp; If the context is switched with an EXECUTE AS the impersonated context is used. This function returns the current database user.&lt;br /&gt;
&lt;br /&gt;
Return type: &lt;b&gt;SYSNAME &lt;/b&gt;&lt;br /&gt;
&lt;h4&gt;SESSION_USER&lt;/h4&gt;SESSION_USER returns the name of the current security context.&amp;nbsp; If the context is switched with an EXECUTE AS the impersonated context is used. This function returns the current database user.&lt;br /&gt;
&lt;br /&gt;
Return type: &lt;b&gt;NVARCHAR(128)&lt;/b&gt;&lt;br /&gt;
&lt;h4&gt;SYSTEM_USER&lt;/h4&gt;SYSTEM_USER returns the name of the current security context.&amp;nbsp; If the context is switched with an EXECUTE AS the impersonated context is used. This function returns the current login, not the database user.&lt;br /&gt;
&lt;br /&gt;
Return type: &lt;b&gt;NCHAR&lt;/b&gt;&lt;br /&gt;
&lt;h4&gt;USER_NAME()&lt;/h4&gt;The USER_NAME() function is principally the same as the USER function.&amp;nbsp; It can optionally take a database_principal_id as an input value and will return the database user of that ID if found.&lt;br /&gt;
&lt;br /&gt;
Return type: &lt;b&gt;NVARCHAR(256)&lt;/b&gt; &lt;br /&gt;
&lt;h4&gt;USER_ID()&lt;/h4&gt;This returns the identification number of the database user.&amp;nbsp; What this means is that it returns the database_principal_id from sys.database_principals.&amp;nbsp; Or if you're stuck on SQL 2000 uid from sysusers.&amp;nbsp; The function can optionally take a database user as input and return the ID for that user if found.&amp;nbsp; If the function is called with the EXECUTE AS statement and no input parameters it returns the ID of the impersonated account.&lt;br /&gt;
&lt;br /&gt;
Return type: &lt;b&gt;INT&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Note:&lt;/b&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt; &lt;i&gt;This function is to be deprecated.&amp;nbsp; It is replaced by the DATABASE_PRINCIPAL_ID() function.&lt;/i&gt;&lt;br /&gt;
&lt;h4&gt;USER_SID()&lt;/h4&gt;This function seems to be strangely absent from Books Online.&amp;nbsp; It returns the security identifier (SID) of current security context.&amp;nbsp; It can optionally take a user id (database_principal_id) value as input.&amp;nbsp; If the context is switched with an EXECUTE AS the impersonated context is used.&lt;br /&gt;
&lt;br /&gt;
Return type: &lt;b&gt;VARBINARY(85)&lt;/b&gt; &lt;br /&gt;
&lt;h4&gt;SUSER_NAME()&lt;/h4&gt;Returns the login name associated with a database user which can optionally be passed into the function. When  called without parameters it takes the current security context.&amp;nbsp; If the  context is switched with an EXECUTE AS the impersonated context is  used.&lt;br /&gt;
&lt;br /&gt;
Return type: &lt;b&gt;NVARCHAR(128)&lt;/b&gt;&lt;br /&gt;
&lt;h4&gt;SUSER_SNAME()&lt;/h4&gt;Returns the login name associated with a security identification  number (SID) which can optionally be passed into the function. When  called without parameters it takes the current security context.&amp;nbsp; If the  context is switched with an EXECUTE AS the impersonated context is  used.&lt;br /&gt;
&lt;br /&gt;
Return type: &lt;b&gt;NVARCHAR(128)&lt;/b&gt; &lt;br /&gt;
&lt;h4&gt;SUSER_ID()&lt;/h4&gt;Returns the login identification number of the user.&amp;nbsp; In SQL 2000 this always returned null.&amp;nbsp; Starting with SQL 2005 this returned the principal_id in sys.server_principals.&amp;nbsp; This function can optionally take the name of a login as input.&amp;nbsp;  If the  security context is switched with an EXECUTE AS the impersonated context is  used.&amp;nbsp; Only if a login is explicitly defined is a valid value returned.&amp;nbsp; Therefore any login that gains access through group inheritance returns the "public" server role sid.&lt;br /&gt;
&lt;br /&gt;
Return type: &lt;b&gt;INT&lt;/b&gt;&lt;br /&gt;
&lt;h4&gt;SUSER_SID()&lt;/h4&gt;Returns the security identification number (SID) for the specified login.&amp;nbsp; When the login is not explicitly passed in the current security context is used. If the  security context is switched with an EXECUTE AS the impersonated context is  used.&lt;br /&gt;
&lt;br /&gt;
Return type: &lt;b&gt;VARBINARY(85)&lt;/b&gt; &lt;br /&gt;
&lt;h4&gt;ORIGINAL_LOGIN()&lt;/h4&gt;Returns the name of the login that connected to the instance of SQL Server.&amp;nbsp; If the security context is switched with an EXECUTE AS statement the &lt;b&gt;original &lt;/b&gt;context is used.&lt;br /&gt;
&lt;br /&gt;
Return type: &lt;b&gt;SYSNAME&lt;/b&gt;&lt;br /&gt;
&lt;h4&gt;DATABASE_PRINCIPAL_ID()&lt;/h4&gt;Returns the ID number of a principal of the current database.&amp;nbsp; It can optionally take the name of a database principal as input.&amp;nbsp; If the principal name is omitted the current database principal is used. If the  security context is switched with an EXECUTE AS the impersonated context is  used.&lt;br /&gt;
&lt;br /&gt;
Return type: &lt;b&gt;INT&lt;/b&gt;&lt;br /&gt;
&lt;h3&gt;Examples&lt;/h3&gt;Thats a lot of information to try and digest. Many of the functions would appear to be identical from their descriptions.&amp;nbsp; Its therefore best to show the subtleties of each function with a few examples.&lt;br /&gt;
&lt;br /&gt;
Throughout these examples, I'll be running the following T-SQL to demonstrate each of the functions in various scenarios:&lt;br /&gt;
&lt;pre class="Sql" name="code"&gt;select [Function] = 'user' ,Result = convert(sql_variant, user) union all
select [Function] = 'current_user' ,Result = convert(sql_variant, current_user) union all
select [Function] = 'sesion_user' ,Result = convert(sql_variant, session_user) union all
select [Function] = 'user_name()' ,Result = convert(sql_variant, user_name()) union all
select [Function] = 'user_id()' ,Result = convert(sql_variant, user_id()) union all 
select [Function] = 'user_sid()' ,Result = convert(sql_variant, user_sid()) union all 
select [Function] = 'system_user' ,Result = convert(sql_variant, system_user) union all 
select [Function] = 'suser_name()' ,Result = convert(sql_variant, suser_name()) union all 
select [Function] = 'suser_sname()' ,Result = convert(sql_variant, suser_sname()) union all 
select [Function] = 'suser_id()' ,Result = convert(sql_variant, suser_id()) union all 
select [Function] = 'suser_sid()' ,Result = convert(sql_variant, suser_sid()) union all 
select [Function] = 'original_login()' ,Result = convert(sql_variant, original_login()) union all
select [Function] = 'database_principal_id()', Result = convert(sql_variant, database_principal_id())
&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;Note:&lt;/b&gt; I wouldn't normally advocate the use of the SQL_VARIANT datatype, but in this case its quite useful to concatenate the results into a single output.&lt;br /&gt;
&lt;br /&gt;
To help understand the screen shots here are some notes about the setup/configuration of the SQL Instance.&lt;br /&gt;
&lt;br /&gt;
&lt;table&gt;&lt;thead&gt;
&lt;tr&gt;&lt;td&gt;&lt;b&gt;Login Type&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;Login Name&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;Database User&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/thead&gt; &lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Windows&lt;/td&gt;&lt;td&gt;BARGHEST\WhoAmI&lt;/td&gt;&lt;td&gt;WhoAmI_win&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;SQL&lt;/td&gt;&lt;td&gt;WhoAmI_std&lt;/td&gt;&lt;td&gt;WhoAmI_db&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;br /&gt;
&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Changing login impersonation&lt;/td&gt;&lt;td&gt;EXECUTE AS LOGIN = 'WhoAmI_std';&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Changing DB impersonation&lt;/td&gt;&lt;td&gt;EXECUTE AS USER = 'WhoAmI_db';&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h4&gt;Windows login no impersonation&lt;/h4&gt;In this scenario we connect to the instance of SQL Server as a windows authenticated account and run the script.&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-r2nOHbXCUvU/Th63SjbuUpI/AAAAAAAAACo/ZNVAsPT3xjg/s1600/01.+Windows+Authentication+No+Impersonation.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-r2nOHbXCUvU/Th63SjbuUpI/AAAAAAAAACo/ZNVAsPT3xjg/s1600/01.+Windows+Authentication+No+Impersonation.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;As you would expect, we see the Windows login used (BARGHEST\WhoAmI) and the associated database user (WhoAmI_win)&lt;br /&gt;
&lt;h4&gt;Standard login no impersonation&lt;/h4&gt;In this scenario we connect to the instance of SQL Server as a SQL authenticated account and run the script.&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-keZkcenxOPA/Th63Sg9JiII/AAAAAAAAACs/UuvNEThMSBc/s1600/01.+SQL+Authentication+No+Impersonation.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-keZkcenxOPA/Th63Sg9JiII/AAAAAAAAACs/UuvNEThMSBc/s1600/01.+SQL+Authentication+No+Impersonation.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;As you would expect, we see the SQL login used (WhoAmI_std) and the associated database user (WhoAmI_db)&lt;br /&gt;
&lt;h4&gt;Windows login impersonating the WhoAmI_std login &lt;/h4&gt;In this scenario we connect to the instance of SQL Server as a windows authenticated account, change the context to the WhoAmI_std login and run the script.&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-2XdGtITsUiI/Th63SvKLPmI/AAAAAAAAACw/piuoxf5_t6k/s1600/02.+Windows+authentication+impersonating+standard+login+-+Copy.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-2XdGtITsUiI/Th63SvKLPmI/AAAAAAAAACw/piuoxf5_t6k/s1600/02.+Windows+authentication+impersonating+standard+login+-+Copy.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Of note here is the fact that all functions return the impersonated login and user with the exception of the ORIGINAL_LOGIN() function.&lt;br /&gt;
&lt;h4&gt;Windows login impersonating the WhoAmI_db user&lt;/h4&gt;In this scenario we connect to the instance of SQL Server as a  windows authenticated account, change the context to the WhoAmI_db user and run the script.&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-NkfcBBwXfKs/Th63Sz7NklI/AAAAAAAAAC0/DPD2SM6Ivtg/s1600/03.+Windows+authentication+impersonating+standard+db+user.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-NkfcBBwXfKs/Th63Sz7NklI/AAAAAAAAAC0/DPD2SM6Ivtg/s1600/03.+Windows+authentication+impersonating+standard+db+user.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
The expected database user (WhoAmI_db) is returned by the functions, what may be of surprise is that the login (WhoAmI_std) associated with the impersonated user is also returned.&lt;br /&gt;
&lt;h4&gt;Standard login impersonating a Windows login&lt;/h4&gt;In this scenario we connect to the instance of SQL Server as a SQL authenticated account, change the context to the BARGHEST\WhoAmI login and run the script.&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-4gm-y4sARl0/TiP8nxXH_YI/AAAAAAAAADA/YKw3UzmTxNw/s1600/Standard%2BLogin%2BImpersonating%2BWindows%2Blogin.png" imageanchor="1"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-4gm-y4sARl0/TiP8nxXH_YI/AAAAAAAAADA/YKw3UzmTxNw/s1600/Standard%2BLogin%2BImpersonating%2BWindows%2Blogin.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h4&gt;Standard login impersonating the db user WhoAmI_db&lt;/h4&gt;In this scenario we connect to the instance of SQL Server as a SQL authenticated account, change the context to the WhoAmI_db user and run the script.&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-kKqkepuFn7M/TiP8s4yWeII/AAAAAAAAADI/1W7iN9lugDc/s1600/Standard%2BLogin%2BImpersonating%2BWindows%2Bdb%2Buser.png" imageanchor="1"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/-kKqkepuFn7M/TiP8s4yWeII/AAAAAAAAADI/1W7iN9lugDc/s1600/Standard%2BLogin%2BImpersonating%2BWindows%2Bdb%2Buser.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h3&gt;Summary&lt;/h3&gt;Each function has its own unique facet that make them each useful in their own right. There's a lot to take in with all these functions and I'm danger of leaving you more confused than when we started.&lt;br /&gt;
&lt;br /&gt;
I haven't even explored using the EXECUTE AS functionality in procedures and will save that for another article as I've presented more than enough here!&lt;br /&gt;
&lt;br /&gt;
The general rule of thumb I follow for remembering the function, and I'm aware its not perfect, is this:&lt;br /&gt;
&lt;div class="quote"&gt;If the function ends ID it returns the database principal&lt;br /&gt;
It the function ends SID it returns the server principal&lt;br /&gt;
If the function ends USER it returns the database user name&lt;br /&gt;
If the function ends NAME it returns the server login name&lt;/div&gt;And like learning a foreign language you just have to remember the exceptions (CURRENT_USER and SESSION_USER anyone?)&lt;br /&gt;
&lt;br /&gt;
I recommend you try out each of the functions yourself, though I think you'll find you settle on one or two you use regularly. For example; traditionally I had used SUSER_SNAME() but now use ORIGINAL_LOGIN() to identify who is making the database call. I'm usually only interested in who the person sat at the keyboard is and therefore ORIGINAL_LOGIN() is the best choice for this.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=o1ZPJ5tiDRk:YR6HHO3J5hE:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=o1ZPJ5tiDRk:YR6HHO3J5hE:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/o1ZPJ5tiDRk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/2049074689301433795/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2011/07/who-am-i.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/2049074689301433795?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/2049074689301433795?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/o1ZPJ5tiDRk/who-am-i.html" title="Who Am I?" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-r2nOHbXCUvU/Th63SjbuUpI/AAAAAAAAACo/ZNVAsPT3xjg/s72-c/01.+Windows+Authentication+No+Impersonation.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2011/07/who-am-i.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkMCSX48fip7ImA9WhdTE0U.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-1440303175244768916</id><published>2011-07-11T23:40:00.001+12:00</published><updated>2011-07-11T23:41:08.076+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-11T23:41:08.076+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="TSQL tips" /><category scheme="http://www.blogger.com/atom/ns#" term="schema compare" /><title>Compare database schema (Part 2)</title><content type="html">In &lt;a href="http://blog.sqlconcepts.co.nz/2011/07/compare-database-schema-part-1.html"&gt;Part 1&lt;/a&gt; I showed how you could quickly, and easily, identify objects (specifically procedures) that had changed.  However, whilst you could see which objects had changed, it wasn't clear &lt;i&gt;what&lt;/i&gt; had changed.&lt;br /&gt;
&lt;br /&gt;
From the first article we saw that the dbo.uspGetBillOfMaterials procedure had changed.&amp;nbsp; Today I'm going to show you how we can identify which line has changed in that particular procedure.&amp;nbsp; To do this, we'll be using a slightly modified version of the "&lt;a href="http://www.sqlservercentral.com/articles/tally+table/72993/"&gt;Tally Oh CSV splitter&lt;/a&gt;" by Jeff Moden.&amp;nbsp; If you haven't read that article I strongly suggest you do - especially if you haven't come across Tally Tables or Number Tables before. They are a very useful SQL trick for "set based iterations".&lt;br /&gt;
&lt;br /&gt;
But back to identifying what has changed in our procedure...&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
I had made a small modification to the "production" version of the procedure.&amp;nbsp; All I have done is add a line that is a comment and changed the MAXRECURSION option at the bottom of the procedure.&amp;nbsp; Here's the last few lines of each procedure to demonstrate:&lt;br /&gt;
&lt;br /&gt;
Original Version:&lt;br /&gt;
&lt;pre class="Sql" name="code"&gt;-- Outer select from the CTE
    SELECT b.[ProductAssemblyID], b.[ComponentID], b.[ComponentDesc], SUM(b.[PerAssemblyQty]) AS [TotalQuantity] , b.[StandardCost], b.[ListPrice], b.[BOMLevel], b.[RecursionLevel]
    FROM [BOM_cte] b
    GROUP BY b.[ComponentID], b.[ComponentDesc], b.[ProductAssemblyID], b.[BOMLevel], b.[RecursionLevel], b.[StandardCost], b.[ListPrice]
    ORDER BY b.[BOMLevel], b.[ProductAssemblyID], b.[ComponentID]
    OPTION (MAXRECURSION 25) 
&lt;/pre&gt;&lt;br /&gt;
Modified Version:&lt;br /&gt;
&lt;pre class="Sql" name="code"&gt;-- Outer select from the CTE
    SELECT b.[ProductAssemblyID], b.[ComponentID], b.[ComponentDesc], SUM(b.[PerAssemblyQty]) AS [TotalQuantity] , b.[StandardCost], b.[ListPrice], b.[BOMLevel], b.[RecursionLevel]
    FROM [BOM_cte] b
    GROUP BY b.[ComponentID], b.[ComponentDesc], b.[ProductAssemblyID], b.[BOMLevel], b.[RecursionLevel], b.[StandardCost], b.[ListPrice]
    ORDER BY b.[BOMLevel], b.[ProductAssemblyID], b.[ComponentID]
 /* Change the MAXRECURSION from 25 to 30 */
    OPTION (MAXRECURSION 30) 
&lt;/pre&gt;&lt;br /&gt;
How can we go about finding those "different" lines?  Enter the afore mentioned "Tally Oh" article.&lt;br /&gt;
&lt;br /&gt;
I'm going to cheat a little and create the dbo.DelimitedSplit8K function in each of the databases concerned.  Once created we can run the following on each of the databases:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="Sql" name="code"&gt;select
 ItemNumber  as LineNumber
 ,Item   as LineText
 ,checksum(Item) as LineChecksum
from
 dbo.DelimitedSplit8K(object_definition(object_id('dbo.uspGetBillOfMaterials')), char(13));
&lt;/pre&gt;&lt;br /&gt;
What we are doing here is splitting the code by the carriage return character: char(13).  &lt;br /&gt;
&lt;br /&gt;
This gives us output resembling:&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-y-k37or0tQY/ThrZ_UetkxI/AAAAAAAAABY/lYbpvcDaCcY/s1600/schema_compare_pt2_linechecksum.jpg" imageanchor="1"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/-y-k37or0tQY/ThrZ_UetkxI/AAAAAAAAABY/lYbpvcDaCcY/s320/schema_compare_pt2_linechecksum.jpg" width="302" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
On a long procedure its going to be hard to find the changed lines.  So take the output and paste it into Excel (or your spreadsheet software of choice) and with the simplest of formulas &lt;br /&gt;
&lt;pre&gt;=IF(C2&amp;lt;&amp;gt;F2,"Different!","")&lt;/pre&gt;We get:&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-TcK06dbpb7E/Thrb80LlCYI/AAAAAAAAABg/7FjKI9GjV3I/s1600/schema_compare_pt2_excel.jpg" imageanchor="1"&gt;&lt;img border="0" height="448" src="http://1.bp.blogspot.com/-TcK06dbpb7E/Thrb80LlCYI/AAAAAAAAABg/7FjKI9GjV3I/s640/schema_compare_pt2_excel.jpg" width="640" /&gt;&amp;nbsp;&lt;/a&gt;&amp;nbsp;&lt;/div&gt;&lt;br /&gt;
Not the prettiest of tools by a long stretch...but we have found the different lines.&lt;br /&gt;
&lt;br /&gt;
Hopefully if you're stuck in the field without access to some handy schema compare tools you'll save some time finding differences in code by applying the checksum across your db!&lt;br /&gt;
&lt;br /&gt;
This doesn't mark the end of this series, I'm exploring some other options and ideas and intend to blog about those in due course - unfortunately my job keeps getting in the way.&amp;nbsp; And we haven't even covered table differences - which can't be had at with the object_definition function.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=CLYKG6j7AyE:tFay_5kwamM:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=CLYKG6j7AyE:tFay_5kwamM:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/CLYKG6j7AyE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/1440303175244768916/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2011/07/compare-database-schema-part-2.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/1440303175244768916?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/1440303175244768916?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/CLYKG6j7AyE/compare-database-schema-part-2.html" title="Compare database schema (Part 2)" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-y-k37or0tQY/ThrZ_UetkxI/AAAAAAAAABY/lYbpvcDaCcY/s72-c/schema_compare_pt2_linechecksum.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2011/07/compare-database-schema-part-2.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUUGQHozfip7ImA9WhdTE0o.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-2746817234313640282</id><published>2011-07-06T23:00:00.003+12:00</published><updated>2011-07-11T21:40:21.486+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-11T21:40:21.486+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="TSQL tips" /><category scheme="http://www.blogger.com/atom/ns#" term="schema compare" /><title>Compare database schema (Part 1)</title><content type="html">The idea for this series of blog posts stems from the fact that not all database administrators and developers get to have access to great tools such as:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.red-gate.com/products/sql-development/sql-compare/"&gt;Red Gate's Schema Compare&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.idera.com/Products/SQL-toolbox/SQL-comparison-toolset/"&gt;Idera SQL Comparison Toolset&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.apexsql.com/sql_tools_diff.aspx"&gt;Apex SQL Compare&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;Even though many of these tools are superb value for money and improve your productivity immensely it can be hard to convince your boss to pay for them.&lt;br /&gt;
&lt;br /&gt;
So what can we do when our purchase order is rejected?&amp;nbsp; And lets face it; in today's climate its harder to get purchase sign off.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
Well there are a couple of Codeplex projects that are worth considering:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;a href="http://dbdiff.codeplex.com/"&gt;DBDiff&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://opendbiff.codeplex.com/"&gt;OpenDBDiff&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;The price of those is hard to beat!&lt;br /&gt;
&lt;br /&gt;
But then perhaps you're working in an environment where installing anything on your work PC is a big "no no!"&amp;nbsp; This is the situation I am currently in and as I'm working as a contractor I really have to honour the policies the company has in place.&amp;nbsp; Believe me the company has them in place for good reason.&lt;br /&gt;
&lt;br /&gt;
So you're stuck with the SQL Server tools and not much else.&lt;br /&gt;
&lt;br /&gt;
For part 1 of this series I have a quick little piece of code we can use to see whether an object's definition is the same between two versions of the database.&amp;nbsp; I have taken a copy of &lt;a href="http://msftdbprodsamples.codeplex.com/"&gt;AdventureWorks2008R2&lt;/a&gt; and created two versions on the same server, but they could just as easily have been on different servers.&amp;nbsp; I have amended one of the procedures by adding a comment.&lt;br /&gt;
&lt;br /&gt;
How can I quickly tell if they're the same or not:&lt;br /&gt;
&lt;pre class="Sql" name="code"&gt;select
    object_schema_name(object_id)     as schema_name
    ,object_name(object_id)       as object_name
    ,checksum(object_definition(object_id))   as code_checksum
from
    sys.procedures
order by
    object_schema_name(object_id)
    ,object_name(object_id)
&lt;/pre&gt;&lt;br /&gt;
If I run this on both copies of the database I get:&lt;br /&gt;
&lt;a href="http://2.bp.blogspot.com/-c8P6NxFQnJ8/ThQ78J8WgQI/AAAAAAAAABM/34TbNOdy5do/s1600/unmodified.png" imageanchor="1"&gt;&lt;img border="0" height="167" src="http://2.bp.blogspot.com/-c8P6NxFQnJ8/ThQ78J8WgQI/AAAAAAAAABM/34TbNOdy5do/s320/unmodified.png" width="320" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
and&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://4.bp.blogspot.com/-FJRvLW1oi3A/ThQ8TeejLMI/AAAAAAAAABQ/Hx719AT4cuA/s1600/modified.png" imageanchor="1"&gt;&lt;img border="0" height="167" src="http://4.bp.blogspot.com/-FJRvLW1oi3A/ThQ8TeejLMI/AAAAAAAAABQ/Hx719AT4cuA/s320/modified.png" width="320" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
If you look closely you'll see that dbo.uspGetBillOfMaterials have different checksums.&amp;nbsp; Therefore there's something different about their definition.&amp;nbsp; Sure we don't yet know &lt;i&gt;what&lt;/i&gt; is different, but at least we only have to compare one procedure and not all of them!&lt;br /&gt;
&lt;br /&gt;
In future entries for this series I plan to show how we can make the comparison of many objects easier and I hope to come up with a way to automate this (but I'm still working on that!).&lt;br /&gt;
&lt;br /&gt;
In the meantime, if you know of some good free or very nearly free comparison tools post them in the comments.&amp;nbsp; I'd like to do bit of an evaluation and do a "round up" post on them as I'm always looking for ways to make my job easier.&lt;br /&gt;
&lt;br /&gt;
And have a think about how else you might use the checksum functions - they seem to get unfairly neglected in my experience and yet they're so handy.&lt;br /&gt;
&lt;br /&gt;
Thanks for reading.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=RylhdkVI1Eg:-HyHhSrNs7M:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=RylhdkVI1Eg:-HyHhSrNs7M:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/RylhdkVI1Eg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/2746817234313640282/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2011/07/compare-database-schema-part-1.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/2746817234313640282?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/2746817234313640282?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/RylhdkVI1Eg/compare-database-schema-part-1.html" title="Compare database schema (Part 1)" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-c8P6NxFQnJ8/ThQ78J8WgQI/AAAAAAAAABM/34TbNOdy5do/s72-c/unmodified.png" height="72" width="72" /><thr:total>2</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2011/07/compare-database-schema-part-1.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEYFR34_eCp7ImA9WhZaGUQ.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-4371116401015712439</id><published>2011-07-04T22:26:00.002+12:00</published><updated>2011-07-07T10:41:56.040+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-07T10:41:56.040+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="TSQL tips" /><title>Count of occurences in a string</title><content type="html">Recently a colleague was importing a pipe delimited file into a table. The file was supposed to be "clean" but they were getting data appearing in the wrong columns and all kinds of data type mismatch errors being thrown all over the place. They suspected there were too many fields in some rows being sent through the file (e.g. they were expecting 10 pipe characters, but were getting 11...or more!). They imported the file into a single column table and then wanted to count the pipe characters.&lt;br /&gt;
&lt;br /&gt;
My initial thought was "strip out the characters you don't want then grab the length of the resulting string". Something akin to this:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="Sql" name="code"&gt;declare @String varchar(max);

set @String = 'The quick |brown fox |jumped over |the lazy |dog';

select len(@String) as OriginalLength,  len(replace(replace(replace(@String, 'a', ''), 'b', ''), 'c', '')) as Characters;
go

&lt;/pre&gt;&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
That approach was going to get "gnarly" way too quickly. So I flipped the problem on its head...&lt;br /&gt;
&lt;br /&gt;
Strip out the characters &lt;b&gt;you want to count&lt;/b&gt; then find the difference in the original and new string lengths. Here's the code I came up with for that approach:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="sql" name="code"&gt;declare @String varchar(max);

set @String = 'The quick |brown fox |jumped over |the lazy |dog';

select len(@String) as OriginalLength, len(@String) - len(replace(@String, '|', '')) as Characters;
go
&lt;/pre&gt;&lt;br /&gt;
First impressions were great! This was too easy.&amp;nbsp; It was actually enough to solve the original problem. However after some further testing I found it contains one tiny, but critical floor.&amp;nbsp; After removing the search character, if the string ends with whitespace characters (space, tab, carriage return etc) it returns incorrect results. Here is an examples to illustrate this:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="sql" name="code"&gt;declare @SampleData table(
 StringData   varchar(max)
 ,PipeCount int
 );

insert into @SampleData(StringData, PipeCount)
values
 ('The quick |brown fox |jumped over |the lazy |dog', 4) -- good count
 ,('The quick |brown fox |jumped over |the lazy |dog ', 4) -- good count
 ,('The quick |brown fox |jumped over |the lazy |dog|', 5) -- good count
 ,('|The quick |brown fox |jumped over |the lazy |dog', 5) -- good count
 ,('The quick |brown fox |jumped over |the lazy |dog |', 5) -- bad count
 ,('| The quick |brown fox |jumped over |the lazy |dog', 5) -- good count
 ;

select PipeCount, len(StringData) - len(replace(StringData, '|', '')) as CalcCount
from
 @SampleData;

go

&lt;/pre&gt;&lt;br /&gt;
&lt;h5&gt;Results&lt;/h5&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;a href="http://2.bp.blogspot.com/-XUjzyjI25j8/ThGRwTs2X4I/AAAAAAAAABE/vvEfXAxV4sk/s1600/len_count.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;/a&gt;&lt;a href="http://2.bp.blogspot.com/-XUjzyjI25j8/ThGRwTs2X4I/AAAAAAAAABE/vvEfXAxV4sk/s1600/len_count.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-XUjzyjI25j8/ThGRwTs2X4I/AAAAAAAAABE/vvEfXAxV4sk/s1600/len_count.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
As you'll see Row 5 counts an extra character and further whitespace characters at the end of the data would increase this discrepancy. Note that a leading space in Row 6 is &lt;b&gt;not truncated&lt;/b&gt; due to trailing spaces only&amp;nbsp; truncated in a varchar column or variable.&lt;br /&gt;
&lt;br /&gt;
I then remembered that the datalength function counts the whitespace characters so with a slight tweak we get:&lt;br /&gt;
&lt;br /&gt;
&lt;pre class="sql" name="code"&gt;declare @SampleData table(
 StringData   varchar(max)
 ,PipeCount int
 );

insert into @SampleData(StringData, PipeCount)
values
 ('The quick |brown fox |jumped over |the lazy |dog', 4) -- good count
 ,('The quick |brown fox |jumped over |the lazy |dog ', 4) -- good count
 ,('The quick |brown fox |jumped over |the lazy |dog|', 5) -- good count
 ,('|The quick |brown fox |jumped over |the lazy |dog', 5) -- good count
 ,('The quick |brown fox |jumped over |the lazy |dog |', 5) -- bad count
 ,('| The quick |brown fox |jumped over |the lazy |dog', 5) -- good count
 ;

select PipeCount, datalength(StringData) - datalength(replace(StringData, '|', '')) as CalcCount
from
 @SampleData;

go
&lt;/pre&gt;&lt;br /&gt;
Results:&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;a href="http://4.bp.blogspot.com/-3NtvfP-Asl4/ThGSYDAYeLI/AAAAAAAAABI/AdQmI5mOCpY/s1600/datalength_count.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-3NtvfP-Asl4/ThGSYDAYeLI/AAAAAAAAABI/AdQmI5mOCpY/s1600/datalength_count.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Success! And a nifty little string occurrences counting routine.&lt;br /&gt;
&lt;br /&gt;
What I particularly like about this approach (aside from the lack of looping) is that it can count any length string within another string - not just individual characters. You can apply it to set based queries without worrying too much about performance. I did do some experiments with performance...I might save that for a part 2 post though.&lt;br /&gt;
&lt;br /&gt;
I could turn this into a UDF for you but I'm sure you're more than capable of that. Hands up who's got the while loop UDF solution to this problem? C'mon...own up! We've all done it :-) However, for your sake and your users sake please replace it with the code above!&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=z1GkMzl8MUY:bAEQLFNM_G0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=z1GkMzl8MUY:bAEQLFNM_G0:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/z1GkMzl8MUY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/4371116401015712439/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2011/07/count-of-occurences-in-string.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/4371116401015712439?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/4371116401015712439?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/z1GkMzl8MUY/count-of-occurences-in-string.html" title="Count of occurences in a string" /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-XUjzyjI25j8/ThGRwTs2X4I/AAAAAAAAABE/vvEfXAxV4sk/s72-c/len_count.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2011/07/count-of-occurences-in-string.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkICSHk4fip7ImA9WhdTFUk.&quot;"><id>tag:blogger.com,1999:blog-2613706866370202422.post-2929405655780923285</id><published>2011-06-30T21:27:00.002+12:00</published><updated>2011-07-13T21:16:09.736+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-13T21:16:09.736+12:00</app:edited><title>And we're off...</title><content type="html">This blog has to start somewhere, so what better way than to describe myself and what I hope to achieve through this blog.&lt;br /&gt;
&lt;h4&gt;A tiny bit about me... &lt;/h4&gt;I'm a SQL Server consultant/contractor/dba (take your pick for today).&amp;nbsp; I first experienced SQL Server some time in the mid to late 90's writing a web based front end for a components database during a summer job whilst at university&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
That summer job landed me my first "real job" after university back at that company where I started out as a NT administrator.&amp;nbsp; We had a SQL Server 6.5 box that seemed to need a lot of attention from time to time and that piqued my interest in becoming a DBA.&amp;nbsp; I guess that makes me another accidental DBA originally.&lt;br /&gt;
&lt;br /&gt;
Over the years I've worked in energy, financial, telecommunication and consulting companies.&amp;nbsp; Sometime amongst all those roles I moved to New Zealand (Auckland).&amp;nbsp; Most recently I've set up my own consulting company to strive out on my own.&amp;nbsp; We'll see how that goes!&lt;br /&gt;
&lt;br /&gt;
I recently had the opportunity to take up the "2IC" post for the &lt;a href="http://www.aucklandsql.com/"&gt;Auckland SQL Users Group&lt;/a&gt; - thanks Dave!&amp;nbsp; So you can catch me there (I look like that dude on the right) or feel free to drop me a line via twitter &lt;a href="http://twitter/sqlconcepts"&gt;@sqlconcepts&lt;/a&gt;.&lt;br /&gt;
&lt;h4&gt;What I hope to achieve with this blog&lt;/h4&gt;In my "more than a decade" in working with SQL Server I've leveraged a lot off the community.&amp;nbsp; And in short I want to put something back there.&amp;nbsp; This blog is part of that process.&lt;br /&gt;
&lt;br /&gt;
Maybe it will spark some debate from time to time, it may serve as a reference for some of my pieces of code (watch out for a "what's in my toolbox" blog article I'm pondering over)...who knows.&amp;nbsp; But if I manage to help a few people make their jobs a little easier, look at a solution another way or teach them something they simply didn't know before I'll feel I've accomplished what I want from this blog.&lt;br /&gt;
&lt;br /&gt;
So what things will we be covering?&amp;nbsp; Essentially anything around SQL Server.&amp;nbsp; It'll mostly be technical covering many aspects of SQL Server such as SSAS, SSRS, SSIS etc and not just the DB engine.&lt;br /&gt;
&lt;br /&gt;
I'm new to this.&amp;nbsp; Be gentle with me.&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=HrE14Qoq9eY:hvg4Cp3-YZ0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/SQLConcepts?a=HrE14Qoq9eY:hvg4Cp3-YZ0:63t7Ie-LG7Y"&gt;&lt;img src="http://feeds.feedburner.com/~ff/SQLConcepts?d=63t7Ie-LG7Y" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SQLConcepts/~4/HrE14Qoq9eY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.sqlconcepts.co.nz/feeds/2929405655780923285/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://blog.sqlconcepts.co.nz/2011/06/and-were-off.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/2929405655780923285?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2613706866370202422/posts/default/2929405655780923285?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SQLConcepts/~3/HrE14Qoq9eY/and-were-off.html" title="And we're off..." /><author><name>Kent Chenery</name><uri>https://plus.google.com/114428461129118095304</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh4.googleusercontent.com/-xQBmB2OuV0c/AAAAAAAAAAI/AAAAAAAAAWE/G6mWk60rB0A/s512-c/photo.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.sqlconcepts.co.nz/2011/06/and-were-off.html</feedburner:origLink></entry></feed>
