<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://www2.sqlblog.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Adam Machanic : Scripts</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/Scripts/default.aspx</link><description>Tags: Scripts</description><dc:language>en</dc:language><generator>CommunityServer 2.1 SP2 (Build: 61129.1)</generator><item><title>Rejuvenated: Script Creates and Drops for Candidate Keys and Referencing Foreign Keys</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2010/04/04/rejuvinated-script-creates-and-drops-for-candidate-keys-and-referencing-foreign-keys.aspx</link><pubDate>Mon, 05 Apr 2010 02:48:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:24043</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>12</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/24043.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=24043</wfw:commentRss><description>Once upon a time it was 2004, and I wrote what I have to say was a pretty cool little script . (Yes, I know the post is dated 2006, but that's because I dropped the ball and failed to back-date the posts when I moved them over here from my prior blog...(&lt;a href="http://www2.sqlblog.com/blogs/adam_machanic/archive/2010/04/04/rejuvinated-script-creates-and-drops-for-candidate-keys-and-referencing-foreign-keys.aspx"&gt;read more&lt;/a&gt;)&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=24043" width="1" height="1"&gt;</description><enclosure url="http://www2.sqlblog.com/blogs/adam_machanic/attachment/24043.ashx" length="10535" type="application/octet-stream" /><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/foreign+keys/default.aspx">foreign keys</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/indexes/default.aspx">indexes</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/primary+keys/default.aspx">primary keys</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/Scripts/default.aspx">Scripts</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/unique+constraints/default.aspx">unique constraints</category></item><item><title>Who is Active? v9.57: Fast, Comprehensive DMV Collection - What's Really Happening on Your Server?</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2009/12/03/who-is-active-v9-57-fast-comprehensive-dmv-collection.aspx</link><pubDate>Thu, 03 Dec 2009 19:59:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:19489</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>23</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/19489.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=19489</wfw:commentRss><description>UPDATE, April 28 2011: Who is Active v9.57 is outdated. Please use v11.00 instead. Happy December, SQLblog readers! My gift to you, just in time for the holidays: The newest "official" release of your favorite SQL Server activity monitoring stored procedure...(&lt;a href="http://www2.sqlblog.com/blogs/adam_machanic/archive/2009/12/03/who-is-active-v9-57-fast-comprehensive-dmv-collection.aspx"&gt;read more&lt;/a&gt;)&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=19489" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/DMVs/default.aspx">DMVs</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/monitoring/default.aspx">monitoring</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/Performance/default.aspx">Performance</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/Scripts/default.aspx">Scripts</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/who+is+active/default.aspx">who is active</category></item><item><title>Who is Active? v8.82 - Harder, Better, Faster, Stronger</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2009/08/20/who-is-active-v8-82-harder-better-faster-stronger.aspx</link><pubDate>Thu, 20 Aug 2009 15:03:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:16149</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>13</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/16149.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=16149</wfw:commentRss><description>I've had a lot of great feedback since my last "official" Who is Active? release-- v8.40, back in March --and since then I've made 42 distinct changes, bringing us all the way to v8.82. My versioning scheme is quite simple: one change, one increment,...(&lt;a href="http://www2.sqlblog.com/blogs/adam_machanic/archive/2009/08/20/who-is-active-v8-82-harder-better-faster-stronger.aspx"&gt;read more&lt;/a&gt;)&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=16149" width="1" height="1"&gt;</description><enclosure url="http://www2.sqlblog.com/blogs/adam_machanic/attachment/16149.ashx" length="17605" type="application/zip" /><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/DMVs/default.aspx">DMVs</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/monitoring/default.aspx">monitoring</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/Scripts/default.aspx">Scripts</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/who+is+active/default.aspx">who is active</category></item><item><title>Demos From Cape Cod .NET: T-SQL Power! Presentation</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2009/06/18/demos-from-cape-cod-net-t-sql-power-presentation.aspx</link><pubDate>Thu, 18 Jun 2009 16:05:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:14749</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>0</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/14749.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=14749</wfw:commentRss><description>On Tuesday night I was honored to present a new talk to the Cape Cod .NET Users Group that meets in Plymouth, MA. The talk is called "T-SQL Power! Learning to Harness the Under-Used OVER Clause". Following is the abstract for the talk: First introduced...(&lt;a href="http://www2.sqlblog.com/blogs/adam_machanic/archive/2009/06/18/demos-from-cape-cod-net-t-sql-power-presentation.aspx"&gt;read more&lt;/a&gt;)&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=14749" width="1" height="1"&gt;</description><enclosure url="http://www2.sqlblog.com/blogs/adam_machanic/attachment/14749.ashx" length="4365" type="application/zip" /><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/demos/default.aspx">demos</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/OVER+clause/default.aspx">OVER clause</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/row_5F00_number/default.aspx">row_number</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/Scripts/default.aspx">Scripts</category></item><item><title>Who is Active? v8.40 - Now With Delta Power!</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2009/03/30/who-is-active-v8-40-now-with-delta-power.aspx</link><pubDate>Mon, 30 Mar 2009 16:41:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:13006</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>26</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/13006.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=13006</wfw:commentRss><description>
&lt;p&gt;&lt;b style="font-size:200%;"&gt;NOTE: v8.40 is outdated at this time. Please try v9.57, &lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2009/12/03/who-is-active-v9-57-fast-comprehensive-dmv-collection.aspx"&gt;which you can find here&lt;/a&gt;.&lt;/b&gt;&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;It has been only a month and a half since I posted the last version of Who is Active? (&lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2009/02/18/who-is-active-v7-30.aspx"&gt;v7.30&lt;/a&gt;),
but in that time I've made a huge number of changes, fixes, and
enhancements. &lt;a href="http://sqlblog.com/blogs/adam_machanic/attachment/13006.ashx"&gt;The new version, v8.40&lt;/a&gt;, is faster, more robust, and
includes a few exciting features. &lt;/p&gt;

&lt;p&gt;&lt;a href="http://sqlblog.com/blogs/adam_machanic/attachment/13006.ashx"&gt;&lt;b&gt;Click here to download Who is Active? v8.40&lt;/b&gt;&lt;/a&gt; &lt;br&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Following is a list of some of the
things I've done with it, in no particular order, along with
descriptions where applicable.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Added a &lt;b&gt;collection interval&lt;/b&gt;
option, @DELTA_INTERVAL. This option causes the script to collect
numeric metrics -- such as reads, writes, and context switches --
twice. The script will wait between collections, based on the duration
passed into the parameter (in seconds). The script will then calculate
the differences (deltas), between the first and second collections, for
any requests that were active for each collection. These deltas will be
output in a new set of columns, reads_delta, writes_delta,
context_switches_delta, etc.&lt;/p&gt;

&lt;p&gt;For example, to use the default options and wait two seconds between collections, you would use the following:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;EXEC sp_WhoIsActive @DELTA_INTERVAL = 2;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;If
you played with Who is Active? in the past you know that the script
outputs a lot of data in a lot of different columns. I started feeling
that the column list was getting unwieldy, so I implemented &lt;b&gt;dynamic column lists&lt;/b&gt;
as a fix. There are two changes here. First of all, the default column
list will only contain columns associated with those features you have
turned on. So for example, if you don't have @GET_LOCKS enabled, the
locks column won't appear.&lt;/p&gt;

&lt;p&gt;The second part of this fix is a new feature to make things even more dynamic: &lt;b&gt;custom column lists&lt;/b&gt;. I sent a slightly earlier test version to &lt;a href="http://blogs.msdn.com/jimmymay/"&gt;Jimmy May&lt;/a&gt;,
and his response was something along the lines of, "it's pretty good,
but I really don't like the column order." To make Jimmy happy, I
implemented the @OUTPUT_COLUMN_LIST option. To use this option, simply
pass in whatever columns you would like to see, in whatever order you
would like to see them, and the script will do the rest. Note that the
output will be the intersection of whatever options are enabled and
whatever columns are found in the list.&lt;/p&gt;

&lt;p&gt;Here's an example I've been using in my own work recently:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;EXEC sp_WhoIsActive &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @OUTPUT_COLUMN_LIST = &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; '[login_name],[dd hh:mm:ss.mss],[sql_text],[reads],[writes],&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; [context_switches],[physical_io],[wait_info],[blocking_session_id],&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; [tempdb_writes],[tran_log_writes],[query_plan],[session_id]';&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Another feature I added that makes things a bit more dynamic and customizable is the &lt;b&gt;ability to sort the output&lt;/b&gt;
based on whatever column and direction you would like. Interested in
seeing the requests that are currently blocked? No need to scroll, just
ask for them to sort high:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;EXEC sp_WhoIsActive &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @SORT_COLUMN = '[blocking_session_id]',&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @SORT_COLUMN_DIRECTION = 'DESC';&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;While
viewing the results on the screen in SSMS is great, sometimes I like to
capture the results to a table for later analysis. So I added a few
features in this most recent version to support &lt;b&gt;automated collection&lt;/b&gt;. &lt;/p&gt;

&lt;p&gt;To
begin with, I realized that the default, nicely-formatted output of the
script, while great for on-screen viewing, isn't good if you want to do
your own analysis later. So I added a new option, @FORMAT_OUTPUT, that
lets you &lt;b&gt;control whether the output is or is not formatted&lt;/b&gt;. The default value for the parameter is 1, keeping with the previous behavior.&lt;/p&gt;

&lt;p&gt;I
also figured that if you want to do some later analysis it might be
nice to know what time period you're working with. So I added a column
called collection_time, which outputs a datetime instance representing
the &lt;b&gt;time that the script finished running&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;Taking things
one step further, I decided that with all of the dynamic column options
it would be a pain to put together a table matching the output schema
if you start playing with different settings. So I made things easy; no
need to figure out what the output looks like; &lt;b&gt;the script will write a CREATE TABLE statement for you&lt;/b&gt;. Simply enable the @RETURN_SCHEMA option and collect the output value from the @SCHEMA parameter.&lt;/p&gt;

&lt;p&gt;Finally,
due to nested INSERT/EXEC limitations, it's impossible to insert the
result into a table when calling the stored procedure. No worries, the
script will &lt;b&gt;insert its output into a table&lt;/b&gt; for you. Just tell it where to send the data, via the @DESTINATION_TABLE option.&lt;/p&gt;

&lt;p&gt;Here's
an example of asking for the unformatted output schema, creating a
destination table based on the current day, and collecting data 10
times, waiting 15 seconds between each collection:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;DECLARE @destination_table VARCHAR(4000);&lt;br&gt;SET @destination_table = 'WhoIsActive_' + CONVERT(VARCHAR, GETDATE(), 112);&lt;br&gt;&lt;br&gt;DECLARE @schema VARCHAR(4000);&lt;br&gt;EXEC sp_WhoIsActive&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @FORMAT_OUTPUT = 0,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @RETURN_SCHEMA = 1,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @SCHEMA = @schema OUTPUT;&lt;br&gt;&lt;br&gt;SET @schema = &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; REPLACE&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; @schema, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; '&amp;lt;table_name&amp;gt;', &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; @destination_table&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; );&lt;br&gt;&lt;br&gt;EXEC(@schema);&lt;br&gt;&lt;br&gt;DECLARE @i INT;&lt;br&gt;SET @i = 0;&lt;br&gt;&lt;br&gt;WHILE @i &amp;lt; 10&lt;br&gt;BEGIN;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; EXEC sp_WhoIsActive&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; @FORMAT_OUTPUT = 0,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; @DESTINATION_TABLE = @destination_table;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; SET @i = @i + 1;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; WAITFOR DELAY '00:00:15'&lt;br&gt;END;&lt;br&gt;GO&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;One of the key reasons I created the &lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2007/12/31/a-gift-of-script-for-2008-who-s-active-what-are-they-doing-and-who-is-blocked.aspx"&gt;original version&lt;/a&gt; of Who is Active? was to help &lt;b&gt;identify blocked and blocking SPIDs&lt;/b&gt;. And I thought it worked pretty well for that purpose until recently, when I discovered that &lt;a href="http://sqlblog.com/blogs/aaron_bertrand/archive/2009/03/21/six-reasons-you-should-be-nervous-about-parallelism.aspx"&gt;sys.dm_exec_requests fails to properly identify blocking when queries go parallel&lt;/a&gt;. Oops. Fixed in the new version; it now uses the waiting tasks DMV for this purpose, instead of the requests DMV.&lt;/p&gt;

&lt;p&gt;As
part of this process I discovered that the joins to the tasks DMVs were
slightly flawed in v7.30, so I fixed them up. And I was able to play
some games to improve performance--enough that I decided to eliminate
the @TASK_INFO option, making this the default behavior. This means
that you'll always get &lt;b&gt;correct blocker identification&lt;/b&gt; from Who
is Active? v.8.40, along with a few bonus task-based metrics including
physical I/O stats and the number of context switches.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Interested in information about transactions? So are most DBAs, which is why I &lt;b&gt;promoted the transaction start time&lt;/b&gt;, which was previously embedded in the tran_log_writes column, &lt;b&gt;to a top-level column&lt;/b&gt;
of its own. This will still populate only with transactions that have
actually done a write, but from what my testers tell me this is okay
behavior. Let me know if you disagree. While I was in there I played
even more performance games, and greatly improved the speed of the
query when the @GET_TRANSACTION_INFO option is enabled.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;A few smaller fixes are also worth mentioning here. First of all, I tested the script against a &lt;b&gt;case-sensitive instance&lt;/b&gt;, and fixed a few issues there. So it should work for you no matter what collation you're using. Second, thanks to &lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2009/03/15/the-grouped-string-concatenation-challenge-is-closed.aspx#comments"&gt;a discussion I had with Roman Nowak&lt;/a&gt;, I was able to finally &lt;b&gt;solve the entitization problem&lt;/b&gt; that plagued the "clickable" query output produced by the script. Third, I put on my security hat and added some code to &lt;b&gt;verify input arguments&lt;/b&gt;, as well as did a review of all of the dynamic SQL used by the script to make sure that &lt;b&gt;it is not injectable&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Thank
you to everyone who sent me feedback and/or feature requests! If you
have an idea for th script, please make sure to send it my way. I have
quite a bit of momentum at the moment and want to keep going and see
just how far I can take this thing. &lt;b&gt;I can only do this with your help&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;Enjoy! &lt;/p&gt;

&lt;p&gt;&lt;a href="http://sqlblog.com/blogs/adam_machanic/attachment/13006.ashx"&gt;&lt;b&gt;Click here to download Who is Active? v8.40&lt;/b&gt;&lt;/a&gt;&lt;br&gt;&lt;/p&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=13006" width="1" height="1"&gt;</description><enclosure url="http://www2.sqlblog.com/blogs/adam_machanic/attachment/13006.ashx" length="14867" type="application/zip" /><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/DMVs/default.aspx">DMVs</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/monitoring/default.aspx">monitoring</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/Scripts/default.aspx">Scripts</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/who+is+active/default.aspx">who is active</category></item><item><title>Who is Active? v7.30</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2009/02/18/who-is-active-v7-30.aspx</link><pubDate>Wed, 18 Feb 2009 15:34:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:11971</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>5</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/11971.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=11971</wfw:commentRss><description>&lt;p&gt;&lt;a href="http://sqlblog.com/blogs/adam_machanic/attachment/11971.ashx"&gt;Who is Active?&lt;/a&gt; is a comprehensive DMV-based monitoring script, designed to tell you at a glance what processes are active on your SQL Server and what they're up to. It has a number of optional features so that you can get results quickly, or monitor deeply, depending on your needs when you happen to be using the script. Today I'm happy to present a new version, v7.30, which includes a few bug fixes an a new feature.&lt;/p&gt;&lt;p&gt;&lt;a href="http://sqlblog.com/blogs/adam_machanic/attachment/11971.ashx"&gt;&lt;b&gt;Click here to download Who is Active? v7.30&lt;/b&gt;&lt;/a&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;Now that I've covered that (for people who haven't been paying attention to this blog as of late), a bit of commentary. After posting Who is Active? &lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2008/12/31/a-gift-of-script-for-2009-who-is-active-redux.aspx"&gt;v7.22 on December 31&lt;/a&gt;, I expected to receive a huge amount of feedback from the community. Alas, there were a few issues:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;It turns out that December 31 is not a great day for a blog post that you would like to get lots of hits.&lt;/li&gt;&lt;li&gt;Apparently many readers were confused about where to actually download the script.&lt;/li&gt;&lt;li&gt;I was told that I need to document the thing better.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Luckily, some users did manage to download it, and I even received some feedback. Great feedback, primarily from a reader named Bill Kan. Thanks for all of your time, Bill!&amp;nbsp; I also discovered a couple of issues on my own, and added a new feature that I thought of while watching &lt;a href="http://scarydba.wordpress.com/"&gt;Grant Fritchey&lt;/a&gt; do an excellent talk at &lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2009/01/16/new-england-data-camp-v1-0-complete-schedule.aspx"&gt;New England Data Camp v1.0&lt;/a&gt;.&lt;br&gt;&lt;br&gt;So now to address those three issues. I'm posting this post on a Wednesday, it's not a holiday, and it will hit midday, so that covers issue #1. For issue #2, see above and see below.&amp;nbsp; I think the download links are painfully obvious at this point.&amp;nbsp; And as for issue #3, well, I'm sorry to say that today is not the day for that one.&amp;nbsp; I've done a fairly good job of documenting the script in the script (download it and take a look), and I think most readers will be able to feel their way around well enough.&amp;nbsp; If you have any questions, post them here and I will be happy to answer. And eventually I will write up a comprehensive document on the thing. But until then, two out of three isn't bad... &lt;br&gt;&lt;br&gt;&lt;a href="http://sqlblog.com/blogs/adam_machanic/attachment/11971.ashx"&gt;&lt;b&gt;Click here to download Who is Active? v7.30&lt;/b&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;Please give it a try and let me know what you think. I've run out of new feature ideas for the moment and could certainly use some input. If you are using it, please let me know what features you've found to
be helpful and how you're integrating it into your work. I'm hoping to
identify a number of use cases as talking points for the forthcoming
document on the script and actual user feedback will be key.&lt;/p&gt;&lt;p&gt;And, most importantly, enjoy! As I mentioned in December, this script has become a vital part of my toolkit; I have to say that it's made certain aspects of monitoring a lot easier and more interesting for me and I hope it does the same for you. &lt;br&gt;&lt;/p&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=11971" width="1" height="1"&gt;</description><enclosure url="http://www2.sqlblog.com/blogs/adam_machanic/attachment/11971.ashx" length="9263" type="application/zip" /><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/DMVs/default.aspx">DMVs</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/monitoring/default.aspx">monitoring</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/Scripts/default.aspx">Scripts</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/who+is+active/default.aspx">who is active</category></item><item><title>A Gift of Script for 2009: Who is Active, Redux</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2008/12/31/a-gift-of-script-for-2009-who-is-active-redux.aspx</link><pubDate>Wed, 31 Dec 2008 14:49:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:10841</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>9</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/10841.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=10841</wfw:commentRss><description>&lt;p&gt;Last year on December 31 &lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2007/12/31/a-gift-of-script-for-2008-who-s-active-what-are-they-doing-and-who-is-blocked.aspx"&gt;I posted part of a larger monitoring script that I had been working on for a few months&lt;/a&gt;. I received lots of great feedback on the little script in both the comments and from people I was working with, and over the course of the last year I estimate that I have invested at least a couple of hundred hours in the script. &lt;a href="http://sqlblog.com/blogs/adam_machanic/attachment/10841.ashx"&gt;The result of all of this time is attached&lt;/a&gt;, and I hope that this script is as useful for others as it has been for me; I've been using it pretty much nonstop while modifying it and it has become an indispensable part of my toolkit. &lt;/p&gt;
&lt;p&gt;Alas, while I had planned to write a comprehensive blog post on the script for publication today, I simply don't have time to do it correctly at the moment--but I wanted to get the script out on the 31st in order to create a kind of tradition. So instead of telling you everything it can do, I will ask that you instead take a look inside the script, where I've done a fairly good job documenting both the parameters and the outputs.&amp;nbsp; I'll do a followup post soon (I hope) and do a better job of telling you about the various options.&amp;nbsp; In the meantime, I'll leave you with a list of the script's major features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Wait stats collection&lt;/b&gt;, if you enable the GET_WAITS switch. This mode aggregates the various resources that the tasks associated with the session are waiting on, and lets you know how long the tasks have been waiting.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Collection of lock information&lt;/b&gt;, if you enable the GET_LOCKS switch. A lot of effort went into this particular feature--all of the locks for each session are aggregated in an XML format, and work is done to map the various identifiers associated with each lock to actual object names so that you don't have to do the footwork yourself.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Information about sleeping SPIDs&lt;/b&gt; holding open transactions, if you enable the GET_TRANSACTION_INFO switch. If you're being blocked by a SPID that is not active, you should be able to find out about it. This option lets you do it.&amp;nbsp; As a bonus, it also pulls information about transaction log writes in any database in which the transaction has done a write.&lt;br&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Collection of the "outer" command/batch&lt;/b&gt;, if you enable the GET_OUTER_COMMAND switch. One of the comments on the script I posted last year asked why, if you issue SQL like "ALTER TABLE dbo.xx ALTER COLUMN yyy FLOAT NULL", you'll end up with statement text that looks like "UPDATE [zzz].[dbo].[xxx] SET [yyy] = [yyy]". This is an internal representation of what's actually happening, but in many cases is not esepecially useful for end-users. So if you want to see what was actually run, now you can.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Every expensive or potentially expensive option is now parameterized&lt;/b&gt;. I spent a lot of time tweaking performance and with no options running the thing is pretty fast. Enable some options--especially lock collection--and it will slow down considerably, so use these advanced features with care.&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Enjoy, and &lt;b&gt;please keep sending feedback&lt;/b&gt;!&amp;nbsp; Let me know what else you would like to see, if you find bugs, etc.&amp;nbsp; I'll post updated versions as major changes are made. &lt;/p&gt;
&lt;p&gt;To finish I would like to thank a few of the people who had an impact on the evolution of this script over the past&amp;nbsp;12 months: Michael Condon, &lt;a href="http://sommarskog.se/"&gt;Erland Sommarskog&lt;/a&gt;, &lt;a href="http://sqlblog.com/blogs/aaron_bertrand/default.aspx"&gt;Aaron Bertrand&lt;/a&gt;, &lt;a href="http://sqlblog.com/blogs/louis_davidson/default.aspx"&gt;Louis Davidson&lt;/a&gt;, and &lt;a href="http://jmkehayias.blogspot.com/"&gt;Jonathan Kehayias&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Have a great 2009, everyone! &lt;br&gt;&lt;/p&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=10841" width="1" height="1"&gt;</description><enclosure url="http://www2.sqlblog.com/blogs/adam_machanic/attachment/10841.ashx" length="8670" type="application/x-zip-compressed" /><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/activity/default.aspx">activity</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/blocking/default.aspx">blocking</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/DMVs/default.aspx">DMVs</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/monitoring/default.aspx">monitoring</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/Performance/default.aspx">Performance</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/Scripts/default.aspx">Scripts</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/who+is+active/default.aspx">who is active</category></item><item><title>A Gift of Script for 2008: Who's Active, What Are They Doing, and Who is Blocked?</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2007/12/31/a-gift-of-script-for-2008-who-s-active-what-are-they-doing-and-who-is-blocked.aspx</link><pubDate>Mon, 31 Dec 2007 22:32:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:4300</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>16</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/4300.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=4300</wfw:commentRss><description>&lt;p&gt;As with all of the blog posts I keep &lt;i&gt;meaning&lt;/i&gt; to write -- I keep a list and given the infrequency with which I've been posting lately, it's getting quite large -- this script has been on the queue for quite some time. So here I find myself with a spare moment right on the cusp of the new year, and figured what better way to end the year than with a script that, at least for me, has been quite useful these last few months.&lt;/p&gt;&lt;p&gt;The driving force behind my writing this script is that I found myself endlessly calling &lt;b&gt;sp_who2 'active'&lt;/b&gt; to see who was doing what on servers I needed to take a look at. Then I would have to sort through the results, and end up calling &lt;b&gt;DBCC INPUTBUFFER&lt;/b&gt; to take a look at the SQL being used. This was a serious pain, and I finally caved a few months back and decided to end the madness once and for all with the help of some DMVs.&lt;/p&gt;&lt;p&gt;The following script primarily uses the &lt;b&gt;sys.dm_exec_requests&lt;/b&gt; view, and finds all "active" requests -- i.e., those that are running, about to start running, or suspended. It also finds some other useful information, including the host name, login name, the start time of the batch, and whether or not the batch is currently blocked. In the outer query I use the &lt;b&gt;sys.dm_exec_sql_text&lt;/b&gt; function to get the text of the SQL that all of the active requests are running, in addition to the SQL being run by the blocking sessions, if applicable. This way I don't have to do two lookups to chase down what's blocking what.&lt;/p&gt;&lt;p&gt;You'll notice that I use &lt;b&gt;FOR XML PATH&lt;/b&gt; in the subqueries that pull the SQL text. This gives us a nice little bonus: instead of copying the text out of the cell in SSMS and pasting it somewhere else, you can simply click on it -- and it maintains whatever formatting, including white space and carriage returns, that it originally had. This is much, much nicer than getting the batch on a single line. The only problem is that certain characters, such as greater-than and less-than, get "entitized" when the text is converted to XML. This means that some queries won't be able to be run without a bit of editing. A small price to pay for nicer output, in my opinion. If anyone out there has a solution for the entitization, please let me know! The only way I know to solve it is to convert back to VARCHAR, and that defeats the whole purpose...&lt;/p&gt;&lt;p&gt;Anyway, thanks all for a great 2007. Here's to an even better 2008! Without further ado, the script:&lt;/p&gt;&lt;blockquote&gt;&amp;nbsp;&lt;/blockquote&gt;&lt;blockquote&gt;SELECT&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; x.session_id,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; x.host_name,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; x.login_name,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; x.start_time,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; x.totalReads,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; x.totalWrites,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; x.totalCPU,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; x.writes_in_tempdb,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SELECT &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; text AS [text()]&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM sys.dm_exec_sql_text(x.sql_handle)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FOR XML PATH(''), TYPE&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ) AS sql_text,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; COALESCE(x.blocking_session_id, 0) AS blocking_session_id,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SELECT &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; p.text&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SELECT &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MIN(sql_handle) AS sql_handle&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM sys.dm_exec_requests r2&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHERE &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; r2.session_id = x.blocking_session_id&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ) AS r_blocking&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; CROSS APPLY&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SELECT &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; text AS [text()]&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM sys.dm_exec_sql_text(r_blocking.sql_handle)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FOR XML PATH(''), TYPE&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ) p (text)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ) AS blocking_text&lt;br&gt;FROM&lt;br&gt;(&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; SELECT&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; r.session_id,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; s.host_name,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; s.login_name,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; r.start_time,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; r.sql_handle,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; r.blocking_session_id,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SUM(r.reads) AS totalReads,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SUM(r.writes) AS totalWrites,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SUM(r.cpu_time) AS totalCPU,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SUM(tsu.user_objects_alloc_page_count + tsu.internal_objects_alloc_page_count) AS writes_in_tempdb&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; FROM sys.dm_exec_requests r&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; JOIN sys.dm_exec_sessions s ON s.session_id = r.session_id&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; JOIN sys.dm_db_task_space_usage tsu ON s.session_id = tsu.session_id and r.request_id = tsu.request_id&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; WHERE r.status IN ('running', 'runnable', 'suspended')&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; GROUP BY&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; r.session_id,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; s.host_name,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; s.login_name,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; r.start_time,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; r.sql_handle,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; r.blocking_session_id&lt;br&gt;) x&lt;br&gt;&lt;/blockquote&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;Enjoy!&lt;br&gt;&lt;/p&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=4300" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/administration/default.aspx">administration</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/DMVs/default.aspx">DMVs</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/Scripts/default.aspx">Scripts</category></item><item><title>Replacing xp_execresultset in SQL Server 2005</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2006/10/19/replacing-xp-execresultset-in-sql-server-2005.aspx</link><pubDate>Thu, 19 Oct 2006 14:13:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:314</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>8</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/314.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=314</wfw:commentRss><description>SQL Server 2000 included a very useful extended stored procedure called &lt;i&gt;xp_execresultset&lt;/i&gt;. This XP had two parameters: &lt;i&gt;@cmd&lt;/i&gt; and &lt;i&gt;@dbname&lt;/i&gt;.
@cmd was expected to be a SELECT statement that would produce a single
column of output, each row of which would produce a valid query.
@dbname was used to specify the database that both the initial query
and the resultant queries would be executed in.&lt;br&gt;&lt;br&gt;For example:&lt;br&gt;&lt;br&gt;&lt;pre class="code"&gt;EXEC xp_execresultset &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @cmd=N'SELECT ''SELECT 1''', &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @dbname=N'tempdb'&lt;/pre&gt;&lt;br&gt;&lt;br&gt;In
this case, tempdb would be used to execute the statement SELECT
''SELECT 1''. This statement produces a single row with the value
SELECT 1, which is then executed in tempdb. The final output is a
single result set containing the value 1.&lt;br&gt;&lt;br&gt;Multi-row statements are also allowed:&lt;br&gt;&lt;br&gt;&lt;pre class="code"&gt;EXEC xp_execresultset &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @cmd=N'SELECT ''SELECT 1'' UNION ALL SELECT ''SELECT 2''', &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @dbname=N'tempdb'&lt;/pre&gt;&lt;br&gt;
&lt;br&gt;In this case, the initial SELECT produces two rows, each with its
own statement. Then each row is processed as an individual query,
thereby producing two single-row result sets, one with the value 1, and
one with the value 2.&lt;br&gt;&lt;br&gt;We can verify that the first statement is run in the specified database by using the DB_ID() function in the outer query:&lt;br&gt;&lt;br&gt;&lt;pre class="code"&gt;USE master&lt;br&gt;&lt;br&gt;EXEC xp_execresultset &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @cmd=N'SELECT ''SELECT ''+CONVERT(NVARCHAR, DB_ID())', &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @dbname=N'tempdb'&lt;/pre&gt;&lt;br&gt;&lt;br&gt;In this case the end result is 2, indicating that the outer query was run in tempdb rather than master.&lt;br&gt;&lt;br&gt;So
now that I've covered xp_execresultset's usage in SQL Server 2000, the
bad news: this XP, although quite useful in many cases, has been
removed in SQL Server 2005. This was pointed out&amp;nbsp; to me by a post today
in the MSDN forums by Marko B. Simic (thanks, Marko!)&lt;br&gt;&lt;br&gt;The
solution, luckily, is pretty simple: We can re-create this XP as a
stored procedure in SQL Server 2005, using a few tricks to make the job
easier. Following is the replacement stored procedure I've come up with:&lt;br&gt;&lt;br&gt;&lt;pre class="code"&gt;CREATE PROC ExecResultSet&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @cmd NVARCHAR(MAX),&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; @dbname NVARCHAR(255)&lt;br&gt;AS&lt;br&gt;BEGIN&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; SET NOCOUNT ON&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; DECLARE @x TABLE&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; sql NVARCHAR(MAX),&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; num INT IDENTITY(1,1)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; )&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; DECLARE @input_sql NVARCHAR(355)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; SET @input_sql = N'EXEC ' + @dbname + '..sp_executesql @stmt=@cmd'&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; INSERT @x (sql)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; EXEC sp_executesql &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; @input_sql, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; N'@cmd NVARCHAR(MAX)', &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; @cmd&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; DECLARE @sql NVARCHAR(MAX)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; SELECT @sql =&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; (&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; SELECT sql + ';' AS [data()]&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; FROM @x&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; ORDER BY num&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; FOR XML PATH('')&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; )&lt;br&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; EXEC sp_executesql &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; @input_sql, &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; N'@cmd NVARCHAR(MAX)', &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; @sql&lt;br&gt;END&lt;br&gt;GO&lt;/pre&gt;&lt;br&gt;&lt;br&gt;Note
that I've tried to faithfully reproduce the original functionality of
xp_execresultset. This means that the resultant rows are concatenated
and executed as a single batch. This can be good in some cases -- for
instance, your first row can contain some variable declarations that
will be used by later rows. However, this can also cause problems if
you need to declare a variable on a per-row basis.&lt;br&gt;&lt;br&gt;This stored
procedure is fairly simple: It executes the input @cmd, concatenates
the resultant rows, and finally executes everything as a single batch.
Its syntax is identical to the original XP. Modifying this stored
procedure to make it a bit more flexible and execute each row as its
own batch is a simple matter of using a cursor to take each row
individually, rather than employing the FOR XML PATH concatenation
trick. However, I'm going to leave it as-is for now so that it can be
used directly in places where you would have used xp_executesql
previously.&lt;br&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=314" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/Scripts/default.aspx">Scripts</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/SQL+Server+2005/default.aspx">SQL Server 2005</category></item><item><title>Looping over routines using sp_foreachroutine</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2006/07/12/looping-over-routines-using-sp-foreachroutine.aspx</link><pubDate>Thu, 13 Jul 2006 01:45:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:101</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>5</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/101.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=101</wfw:commentRss><description>Of all of the undocumented stored procedures shipped with SQL Server, there are two in particular that I constantly use: &lt;a href="http://www.databasejournal.com/features/mssql/article.php/3441031" target="#"&gt;sp_MSforeachtable and sp_MSforeachdb&lt;/a&gt;.
These procedures internally loop over each non-Microsoft shipped (i.e.
user-defined) table in the current database, or each database on the
current server, respectively. During this loop, the procedures perform
whatever action(s) are specified by the user (in the parameters). For
instance, what if you want to re-index every table in the database?
Sure, you could write your own cursor, but &lt;i&gt;why bother?&lt;/i&gt;  Use the following T-SQL instead:
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;EXEC sp_MSforeachtable 'DBCC DBREINDEX(''?'')'&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
Convenient, isn't it? But I won't get into any more detail on these.
Gregory Larsen does a good job of that in the article linked above.
&lt;/p&gt;&lt;p&gt;What I'd like to show instead is a very simple modification
I've made to sp_MSforeachtable. It's great to loop over tables and
databases, but sometimes we want to loop over &lt;i&gt;routines&lt;/i&gt;
(a collective term for procedures, functions, triggers, and views)
instead. Perhaps you want to grant pemissions to a user. Or perhaps you
want to roll out some &lt;a href="http://www.datamanipulation.net/TSQLMacro"&gt;TSQLMacro&lt;/a&gt;
updates to every routine in the database instead of just one, as is
supported by the current version of the framework... And now you know
how it will be done in the next version.
&lt;/p&gt;&lt;p&gt;
Presenting &lt;b&gt;sp_foreachroutine&lt;/b&gt;:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;CREATE PROCEDURE dbo.sp_foreachroutine&lt;br&gt;	@command1 nvarchar(2000), &lt;br&gt;	@replacechar nchar(1) = N'?', &lt;br&gt;	@command2 nvarchar(2000) = null,&lt;br&gt;	@command3 nvarchar(2000) = null, &lt;br&gt;	@whereand nvarchar(2000) = null,&lt;br&gt;	@precommand nvarchar(2000) = null, &lt;br&gt;	@postcommand nvarchar(2000) = null,&lt;br&gt;	@routinetype nvarchar(20) = null&lt;br&gt;AS&lt;br&gt;BEGIN&lt;br&gt;	/* This proc returns one or more rows for each procedure (optionally, matching @where), &lt;br&gt;		with each procedure defaulting to its own result set */&lt;br&gt;	/* @precommand and @postcommand may be used to force a single result set via a temp table. */&lt;br&gt;&lt;br&gt;	/* Preprocessor won't replace within quotes so have to use str(). */&lt;br&gt;	declare @mscat nvarchar(12)&lt;br&gt;	select @mscat = ltrim(str(convert(int, 0x0002)))&lt;br&gt;&lt;br&gt;	if (@precommand is not null)&lt;br&gt;		exec(@precommand)&lt;br&gt;&lt;br&gt;	/* Create the select */&lt;br&gt;&lt;br&gt;	declare @sql nvarchar(4000)&lt;br&gt;	set @sql =&lt;br&gt;		N'declare hCForEach cursor global for ' &lt;br&gt;		 + N' select ''['' + REPLACE(user_name(uid), N'']'', N'']]'') + '']'' + ''.'' + ' &lt;br&gt;			+ N' ''['' + REPLACE(object_name(id), N'']'', N'']]'') + '']'' ' &lt;br&gt;		 + N' from dbo.sysobjects o '&lt;br&gt;	         + N' where OBJECTPROPERTY(o.id, N''IsMSShipped'') = 0 '&lt;br&gt;		 + 	CASE @routinetype&lt;br&gt;				WHEN 'procedure' THEN ' and OBJECTPROPERTY(o.id, N''IsProcedure'') = 1 '&lt;br&gt;				WHEN 'function' THEN ' and (OBJECTPROPERTY(o.id, N''IsScalarFunction'') = 1 '&lt;br&gt;					+ ' or OBJECTPROPERTY(o.id, N''IsTableFunction'') = 1) '&lt;br&gt;				WHEN 'view' THEN ' and OBJECTPROPERTY(o.id, N''IsView'') = 1 '&lt;br&gt;				WHEN 'trigger' THEN ' and OBJECTPROPERTY(o.id, N''IsTrigger'') = 1 '&lt;br&gt;				ELSE ' and ( ' &lt;br&gt;					+ ' OBJECTPROPERTY(o.id, N''IsProcedure'') = 1 '&lt;br&gt;					+ ' or OBJECTPROPERTY(o.id, N''IsScalarFunction'') = 1 '&lt;br&gt;					+ ' or OBJECTPROPERTY(o.id, N''IsTableFunction'') = 1 '&lt;br&gt;					+ ' or OBJECTPROPERTY(o.id, N''IsView'') = 1 '&lt;br&gt;					+ ' or OBJECTPROPERTY(o.id, N''IsTrigger'') = 1 '&lt;br&gt;					+ ' ) '&lt;br&gt;			END&lt;br&gt;	         + COALESCE(@whereand, '')&lt;br&gt;&lt;br&gt;	exec(@sql)&lt;br&gt;	declare @retval int&lt;br&gt;	select @retval = @@error&lt;br&gt;	if (@retval = 0)&lt;br&gt;		exec @retval = sp_MSforeach_worker @command1, @replacechar, @command2, @command3&lt;br&gt;&lt;br&gt;	if (@retval = 0 and @postcommand is not null)&lt;br&gt;		exec(@postcommand)&lt;br&gt;&lt;br&gt;	return @retval&lt;br&gt;END&lt;br&gt;GO&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;Regular readers of this blog will note that the formatting isn't
consistent with my usual standards. But since this was a port from an
MS-written proc, I decided to keep things fairly consistent with what
was already there. I've also added an additional parameter that wasn't
present in sp_MSforeachtable: @routinetype, which lets the user select
a specific type of routine to loop over. So, for instance, if you only
want views, pass in 'view'. Same for functions ('function'), triggers
('trigger') and procedures ('procedure'). Pass in any other value -- or
leave it NULL -- and you'll get all routines in the database.
&lt;/p&gt;&lt;p&gt;This procedure keeps the sp_ prefix on purpose; it's meant to
be created in the master database, and makes use of the MS-shipped
sp_MSforeach_worker stored procedure, which lets it do its work.
&lt;/p&gt;&lt;p&gt;Using it is simple. ? is the default substitution character
(this can be changed using the @replacechar parameter). So to print a
list of all routines in the current database, use:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;EXEC sp_foreachroutine 'print ''?'''&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
For just functions, use the optional @routinetype parameter:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;EXEC sp_foreachroutine 'print ''?''', @routinetype = 'function'&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
Enjoy!&lt;/p&gt;&lt;br&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=101" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/Scripts/default.aspx">Scripts</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/T-SQL/default.aspx">T-SQL</category></item><item><title>Tokenize UDF</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2006/07/12/tokenize-udf.aspx</link><pubDate>Thu, 13 Jul 2006 01:34:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:92</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>1</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/92.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=92</wfw:commentRss><description>Yes, another string splitting UDF from a guy who's obvioiusly become
obsessed with TSQL string splitting. This time we delve into a
mysterious world that I call, "Tokenization."
&lt;p&gt;
So what is Tokenization?  It's a word I made up for this problem.
&lt;/p&gt;&lt;p&gt;But what is it, really? It's splitting up a string based on a
delimiter -- in this case, a comma -- but being wary of substring
delimiters. In this case, that's a pair of apostrophes, because that's
what TSQL uses for strings.
&lt;/p&gt;&lt;p&gt;
I think this is best illustrated with an example string:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="CODE"&gt;DECLARE @Tokens VARCHAR(50)&lt;br&gt;&lt;br&gt;SET @Tokens = 'a, ''b'', ''''c'', ''d'', ''e'''', f, ''1,2,3,4'''&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
The basic &lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/splitting-a-string-of-unlimited-length.aspx"&gt;split string&lt;/a&gt; function that you can find will produce the following output:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;SELECT * &lt;br&gt;FROM dbo.SplitString(@Tokens, ',')&lt;br&gt;&lt;br&gt;OutParam&lt;br&gt;-------------&lt;br&gt;a&lt;br&gt;'b'&lt;br&gt;''c'&lt;br&gt;'d'&lt;br&gt;'e''&lt;br&gt;f&lt;br&gt;'1&lt;br&gt;2&lt;br&gt;3&lt;br&gt;4'&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
Well, that's &lt;i&gt;wrong&lt;/i&gt;.  Because what I want to do is maintain the substrings (or, "tokens," as I like to call them -- thus, Tokenization!)
&lt;/p&gt;&lt;p&gt;
The output I desire is:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="CODE"&gt;Token&lt;br&gt;--------&lt;br&gt;a&lt;br&gt;'b'&lt;br&gt;''c', 'd', 'e''&lt;br&gt;f&lt;br&gt;'1,2,3,4'&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
Notice that substrings -- delimited with apostrophes -- should be maintained.
&lt;/p&gt;&lt;p&gt;
And here's how I've solved this problem...
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="CODE"&gt;CREATE FUNCTION dbo.Tokenize&lt;br&gt;(&lt;br&gt;	@Input NVARCHAR(2000)&lt;br&gt;)&lt;br&gt;RETURNS @Tokens TABLE &lt;br&gt;	(&lt;br&gt;		TokenNum INT IDENTITY(1,1),&lt;br&gt;		Token NVARCHAR(2000)&lt;br&gt;	)&lt;br&gt;AS&lt;br&gt;BEGIN&lt;br&gt;	DECLARE @i INT SET @i = 0&lt;br&gt;	DECLARE @StartChar INT SET @StartChar = 1&lt;br&gt;	DECLARE @Quote INT SET @Quote = 0	&lt;br&gt;&lt;br&gt;	DECLARE @Chars TABLE &lt;br&gt;	(&lt;br&gt;		CharNum INT IDENTITY(1,1) NOT NULL PRIMARY KEY,&lt;br&gt;		TheChar CHAR(1), &lt;br&gt;		TheCount INT,&lt;br&gt;		StartChar INT&lt;br&gt;	)&lt;br&gt;&lt;br&gt;	SET @Input = ' , ' + @Input + ' , '&lt;br&gt;	&lt;br&gt;	INSERT @Chars (TheChar)&lt;br&gt;	SELECT SUBSTRING(@Input, n.Number, 1)&lt;br&gt;	FROM Numbers n&lt;br&gt;	WHERE n.Number &amp;gt; 0 &lt;br&gt;		AND n.Number &amp;lt;= LEN(@Input)&lt;br&gt;	ORDER BY n.Number&lt;br&gt;	&lt;br&gt;	UPDATE Chars SET &lt;br&gt;		@i = Chars.TheCount = &lt;br&gt;			CASE &lt;br&gt;				WHEN Chars1.TheChar = ',' &lt;br&gt;					AND @Quote % 2 = 0 THEN 0 &lt;br&gt;				ELSE @i + 1 &lt;br&gt;			END,&lt;br&gt;		@Quote = &lt;br&gt;			CASE  &lt;br&gt;				WHEN Chars1.TheChar = '''' THEN @Quote + 1 &lt;br&gt;				WHEN @i = 0 THEN 0 &lt;br&gt;				ELSE @Quote &lt;br&gt;			END,&lt;br&gt;		@StartChar = Chars.StartChar =&lt;br&gt;			CASE&lt;br&gt;				WHEN @i = 1 THEN Chars1.CharNum - 1&lt;br&gt;				WHEN @i = 0 THEN @StartChar + 1&lt;br&gt;				ELSE @StartChar&lt;br&gt;			END&lt;br&gt;	FROM @Chars Chars&lt;br&gt;	JOIN @Chars Chars1 ON Chars1.CharNum = Chars.CharNum + 1&lt;br&gt;&lt;br&gt;	INSERT @Tokens(Token)&lt;br&gt;	SELECT&lt;br&gt;		RTRIM(LTRIM(SUBSTRING(@Input, StartChar, CharNum - StartChar + 1)))&lt;br&gt;	FROM (&lt;br&gt;		SELECT StartChar, CharNum&lt;br&gt;		FROM @Chars&lt;br&gt;		WHERE TheCount = 0&lt;br&gt;&lt;br&gt;		UNION ALL&lt;br&gt;&lt;br&gt;		SELECT &lt;br&gt;			MAX&lt;br&gt;			(&lt;br&gt;				CASE TheCount &lt;br&gt;					WHEN 0 THEN CharNum &lt;br&gt;					ELSE 0 &lt;br&gt;				END&lt;br&gt;			) + 1, &lt;br&gt;			MAX(CharNum)&lt;br&gt;		FROM @Chars&lt;br&gt;	) x&lt;br&gt;	WHERE RTRIM(LTRIM(SUBSTRING(@Input, StartChar, CharNum - StartChar + 1))) NOT IN ('', ',')&lt;br&gt;	ORDER BY x.StartChar&lt;br&gt;	RETURN&lt;br&gt;END&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;A word of warning: This UDF uses the undocumented -- and unsupported
-- "aggregate update" functionality. I've tested thoroughly in this
case and believe it works perfectly (and it sure is handy!), but I
would advise you to not use it in your own projects without &lt;i&gt;extensive testing&lt;/i&gt;!  MS doesn't support this one, so handle with care.
&lt;/p&gt;&lt;p&gt;
And by the way, you need a &lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/you-require-a-numbers-table.aspx"&gt;numbers table&lt;/a&gt; to use this thing.  Of course.
&lt;/p&gt;&lt;p&gt;
As for using this thing, it's pretty easy:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="CODE"&gt;DECLARE @Tokens VARCHAR(50)&lt;br&gt;&lt;br&gt;SET @Tokens = 'a, ''b'', ''''c'', ''d'', ''e'''', f, ''1,2,3,4'''&lt;br&gt;&lt;br&gt;SELECT Token&lt;br&gt;FROM dbo.Tokenize(@Tokens)&lt;br&gt;&lt;br&gt;&lt;br&gt;Token&lt;br&gt;--------&lt;br&gt;a&lt;br&gt;'b'&lt;br&gt;''c', 'd', 'e''&lt;br&gt;f&lt;br&gt;'1,2,3,4'&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
... and it even appears to work properly!
&lt;/p&gt;&lt;p&gt;
Enjoy... and application for this and other strange things I've been posting recently coming very, very soon.&lt;/p&gt;&lt;br&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=92" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/Scripts/default.aspx">Scripts</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/T-SQL/default.aspx">T-SQL</category></item><item><title>"Reflect" a TSQL routine</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2006/07/12/reflect-a-tsql-routine.aspx</link><pubDate>Thu, 13 Jul 2006 01:33:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:91</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>0</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/91.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=91</wfw:commentRss><description>Ever want to see the text of a stored procedure, function, or trigger -- or manipulate the text in some way?
&lt;p&gt;
sp_helptext works, sort of. But I really don't like the way it handles
large procedures (&amp;gt; 4000 characters). They seem to end up with some
strangely wrapped lines and other side-effects that aren't too nice. So
I decided to roll my own custom UDF to do the job. The UDF returns a
table containing two columns -- a line number (in increments of 100),
and a line of text, with a maximum of 4000 characters. Yes, that's a
limitation, but if you have stored procedures with line numbers longer
than 4000 characters, you probably have much, much bigger problems than
can be solved reading this blog.
&lt;/p&gt;&lt;p&gt;A friend of mine felt that a parallel of some sort could be
drawn between this UDF and .NET reflection, so I named it
"ReflectRoutine". I admit, the connection is somewhat tenuous, but it
sounds cool, so I'm leaving it. If you can think of a better name, feel
free to rename it on your end!
&lt;/p&gt;&lt;p&gt;I've commented this one quite heavily -- it uses a lot of
string manipulation and can get very confusing. At least, it was
incredibly confusing to write! So without further ado...
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;CREATE FUNCTION dbo.ReflectRoutine&lt;br&gt;(&lt;br&gt;	@RoutineName VARCHAR(200)&lt;br&gt;)&lt;br&gt;RETURNS @ReturnTbl TABLE &lt;br&gt;	(&lt;br&gt;		LineNum INT IDENTITY(100, 100), &lt;br&gt;		OutParam NVARCHAR(4000)&lt;br&gt;	)&lt;br&gt;AS&lt;br&gt;BEGIN&lt;br&gt;	/* n = current colid for the object */&lt;br&gt;	DECLARE @n TINYINT&lt;br&gt;	SET @n = 1&lt;br&gt;&lt;br&gt;	/* current is the current line of text we're working on */&lt;br&gt;	DECLARE @Current NVARCHAR(4000)&lt;br&gt;	SET @Current = ''&lt;br&gt;	DECLARE @CurrentLen INT&lt;br&gt;	SET @CurrentLen = 0&lt;br&gt;&lt;br&gt;	/* overflow and overflow2 will contain &lt;br&gt;	characters too big for the current working set */&lt;br&gt;	DECLARE @Overflow NVARCHAR(4000)&lt;br&gt;	SET @Overflow = ''&lt;br&gt;	DECLARE @OverflowLen INT&lt;br&gt;	SET @OverflowLen = 0&lt;br&gt;	DECLARE @Overflow2 NVARCHAR(4000)&lt;br&gt;	SET @Overflow2 = ''&lt;br&gt;&lt;br&gt;	/* delimiter is our delimiter for string splitting -- crlf */&lt;br&gt;	DECLARE @Delimiter NCHAR(2)&lt;br&gt;	SET @Delimiter = NCHAR(13)+NCHAR(10)&lt;br&gt;&lt;br&gt;	/* how many rows of 4000 characters are we dealing with? */&lt;br&gt;	DECLARE @maxColId TINYINT&lt;br&gt;	SELECT &lt;br&gt;		@maxColId = MAX(colid) + 1&lt;br&gt;	from dbo.syscomments&lt;br&gt;	WHERE id = OBJECT_ID(@RoutineName)&lt;br&gt;&lt;br&gt;	WHILE @n &amp;lt;= @maxColId&lt;br&gt;	BEGIN&lt;br&gt;		/* get the next row of data */&lt;br&gt;		IF @n &amp;lt; @maxColId&lt;br&gt;		BEGIN&lt;br&gt;			SELECT @Current = [text]&lt;br&gt;			FROM dbo.syscomments&lt;br&gt;			WHERE id = OBJECT_ID(@RoutineName)&lt;br&gt;				AND ColId = @n&lt;br&gt;		END&lt;br&gt;		ELSE&lt;br&gt;		BEGIN&lt;br&gt;			/* Work only with the overflow */&lt;br&gt;			SELECT @Current = ''&lt;br&gt;		END&lt;br&gt;&lt;br&gt;		SET @CurrentLen = DATALENGTH(@Current)/2&lt;br&gt;&lt;br&gt;		/* The current overflow value will be pushed into the front&lt;br&gt;		of the current row -- we need to pull off enough characters from the &lt;br&gt;		end to make room for the current overflow plus the current string&lt;br&gt;		and end up with at most 4000 characters.  Overflow2 is the characters&lt;br&gt;		that will be pulled from the end */&lt;br&gt;&lt;br&gt;		SET @Overflow2 = &lt;br&gt;			RIGHT(@Current,&lt;br&gt;				CASE&lt;br&gt;					WHEN (@CurrentLen + @OverflowLen) &amp;gt; 4000 THEN &lt;br&gt;						(@CurrentLen + @OverflowLen) - 4000&lt;br&gt;					ELSE 0&lt;br&gt;				END)&lt;br&gt;&lt;br&gt;		/* Now we push the previous overflow into the front of the value and&lt;br&gt;		get rid of the characters from the end that are now in overflow2 --&lt;br&gt;		leaving us with a current value that's at most 4000 characters (which &lt;br&gt;		is the limit for nvarchar) */&lt;br&gt;&lt;br&gt;		SET @Current = @Overflow + LEFT(@Current, @CurrentLen - DATALENGTH(@Overflow2)/2)&lt;br&gt;		SET @CurrentLen = DATALENGTH(@Current)/2&lt;br&gt;&lt;br&gt;		/* Re-calculate the overflow value.  Find the last occurrence of the &lt;br&gt;		delimiter (crlf) within the first 3996 characters of the string.  The&lt;br&gt;		rest of the string after that delimiter will be split on the next&lt;br&gt;		round (assume that there could be some concatenation needed with the&lt;br&gt;		next chunk, so we can't	split until that's done) &lt;br&gt;&lt;br&gt;		Also, surround the current value with two delimiters so that the split&lt;br&gt;		algorithm will work properly */&lt;br&gt;&lt;br&gt;		SELECT @Overflow = RIGHT(@Current, &lt;br&gt;			CASE &lt;br&gt;				WHEN @CurrentLen - MAX(Number) - 1 &amp;lt; 0 THEN 0&lt;br&gt;				ELSE @CurrentLen - MAX(Number) - 1&lt;br&gt;			END) + @Overflow2,&lt;br&gt;			@Current = @Delimiter + LEFT(@Current, MAX(Number) - 1) + @Delimiter&lt;br&gt;		FROM dbo.Numbers&lt;br&gt;		WHERE (SUBSTRING(@Current, Number, 2) = @Delimiter&lt;br&gt;				OR Number = @CurrentLen + 1)&lt;br&gt;			AND Number &amp;lt;= 3996&lt;br&gt;&lt;br&gt;		SET @CurrentLen = DATALENGTH(@Current)/2&lt;br&gt;		SET @OverflowLen = DATALENGTH(@Overflow)/2&lt;br&gt;&lt;br&gt;		/* finally, insert the lines for this round... */&lt;br&gt;		INSERT	@ReturnTbl (OutParam)&lt;br&gt;		SELECT	SUBSTRING(@Current, Number + 2,&lt;br&gt;				CHARINDEX(@Delimiter, @Current, Number + 2) - Number - 2)&lt;br&gt;		FROM	dbo.Numbers&lt;br&gt;		WHERE 	Number &amp;lt;= @CurrentLen - 2&lt;br&gt;			AND SUBSTRING(@Current, Number, 2) = @Delimiter&lt;br&gt;			AND @CurrentLen &amp;gt; 4&lt;br&gt;		ORDER BY Number&lt;br&gt;&lt;br&gt;		SET @n = @n + 1&lt;br&gt;	END&lt;br&gt;&lt;br&gt;	RETURN&lt;br&gt;END&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
Note, it requires a &lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/you-require-a-numbers-table.aspx"&gt;numbers table&lt;/a&gt; -- but regular readers will probably ask, "what doesn't?"
&lt;/p&gt;&lt;p&gt;
Using it is quite easy... For instance, once it's created if you wish to reflect the UDF itself:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;SELECT *&lt;br&gt;FROM dbo.ReflectRoutine('ReflectRoutine')&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;I recommend Query Analyzer's "Results in Text" mode with "maximum
characters per column" set to a number greater than 4000 -- of course,
none of my stored procedures have lines greater than about 150
characters long, so that's probably not a huge deal for most people. As
I said, if your lines are that long, you have bigger problems!
&lt;/p&gt;&lt;p&gt;
So does this thing have any application?  Watch this space.  More on that coming soon...&lt;br&gt;
&lt;br&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;b&gt;Update, December 1, 2005&lt;/b&gt;: Don't use LEN when you mean DATALENGTH! I
used LEN in the original version of this function and it caused some
strange truncations to occur when an overflow happened right at some
white space (LEN does not count white space to the right). That bug is fixed
in the version now posted. By the way, in case you're reading this for
the first time, the application for this is &lt;a href="http://www.datamanipulation.net/tsqlmacro"&gt;TSQLMacro&lt;/a&gt;.&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=91" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/Scripts/default.aspx">Scripts</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/T-SQL/default.aspx">T-SQL</category></item><item><title>Script out PKs/UNIQUE constraints and referencing FKs</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2006/07/12/script-out-pks-unique-constraints-and-referencing-fks.aspx</link><pubDate>Thu, 13 Jul 2006 01:32:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:90</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>17</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/90.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=90</wfw:commentRss><description>In the course of my work, I occasionally need to cluster a primary key
that's nonclustered, or go the other way, or make some other
modification to a primary key...
&lt;p&gt;But it's a hassle! All of the foreign keys need to be dropped,
the PK needs to be dropped, and then everything needs to be re-created.
Scripting all of that stuff out can be very annoying.
&lt;/p&gt;&lt;p&gt;With that annoyance in mind, I've written this stored
procedure. Put Query Analyzer into Results in Text mode and run
ScriptTableConstraints for a table. It will script out DROPs and
CREATEs for all primary keys, unique keys, and any foreign keys that
reference them -- in the right order. Foreign keys will be dropped
first, then any non-clustered PK or UNIQUE constraints, then clustered
constraints. Keys will be scripted for re-creation in the reverse
order.
&lt;/p&gt;&lt;p&gt;
... And that's it.  This should take some of the annoyance out of doing this kind of work.  Here is the stored procedure:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;CREATE PROCEDURE ScriptTableConstraints&lt;br&gt;	@TableName VARCHAR(100)&lt;br&gt;AS&lt;br&gt;BEGIN&lt;br&gt;	SET NOCOUNT ON&lt;br&gt;&lt;br&gt;	SELECT 'ALTER TABLE [' + TC.TABLE_SCHEMA + '].[' + TC.TABLE_NAME + ']' + CHAR(13)+CHAR(10)+ &lt;br&gt;		'DROP CONSTRAINT [' + TC.CONSTRAINT_NAME + ']' + CHAR(13)+CHAR(10)+CHAR(13)+CHAR(10) &lt;br&gt;		AS [-- Drop Constraints]&lt;br&gt;	FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC&lt;br&gt;	LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC ON RC.CONSTRAINT_NAME = TC.CONSTRAINT_NAME&lt;br&gt;	LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS FC ON FC.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME&lt;br&gt;	WHERE (TC.CONSTRAINT_TYPE IN ('PRIMARY KEY', 'UNIQUE') AND TC.TABLE_NAME = @TableName)&lt;br&gt;		OR (TC.CONSTRAINT_TYPE = 'FOREIGN KEY' AND FC.TABLE_NAME = @TableName)&lt;br&gt;	ORDER BY FC.TABLE_NAME DESC,&lt;br&gt;		INDEXPROPERTY(OBJECT_ID(TC.TABLE_NAME), TC.CONSTRAINT_NAME, 'IsClustered') ASC&lt;br&gt;	&lt;br&gt;	&lt;br&gt;	SELECT 'ALTER TABLE [' + TC.TABLE_SCHEMA + '].[' + TC.TABLE_NAME + ']' + CHAR(13)+CHAR(10)+ &lt;br&gt;		'ADD CONSTRAINT [' + TC.CONSTRAINT_NAME + ']' + CHAR(13)+CHAR(10)+&lt;br&gt;		'  ' + TC.CONSTRAINT_TYPE + &lt;br&gt;			CASE INDEXPROPERTY(OBJECT_ID(TC.TABLE_NAME), TC.CONSTRAINT_NAME, 'IsClustered') &lt;br&gt;				WHEN 1 THEN ' CLUSTERED' &lt;br&gt;				ELSE ' NONCLUSTERED' &lt;br&gt;			END + CHAR(13)+CHAR(10)+&lt;br&gt;		'  (' +&lt;br&gt;			MAX(CASE KCU.ORDINAL_POSITION WHEN 1 THEN '[' + KCU.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU.ORDINAL_POSITION WHEN 2 THEN ', [' + KCU.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU.ORDINAL_POSITION WHEN 3 THEN ', [' + KCU.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU.ORDINAL_POSITION WHEN 4 THEN ', [' + KCU.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU.ORDINAL_POSITION WHEN 5 THEN ', [' + KCU.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU.ORDINAL_POSITION WHEN 6 THEN ', [' + KCU.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU.ORDINAL_POSITION WHEN 7 THEN ', [' + KCU.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU.ORDINAL_POSITION WHEN 8 THEN ', [' + KCU.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU.ORDINAL_POSITION WHEN 9 THEN ', [' + KCU.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU.ORDINAL_POSITION WHEN 10 THEN ', [' + KCU.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU.ORDINAL_POSITION WHEN 11 THEN ', [' + KCU.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU.ORDINAL_POSITION WHEN 12 THEN ', [' + KCU.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU.ORDINAL_POSITION WHEN 13 THEN ', [' + KCU.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU.ORDINAL_POSITION WHEN 14 THEN ', [' + KCU.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU.ORDINAL_POSITION WHEN 15 THEN ', [' + KCU.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU.ORDINAL_POSITION WHEN 16 THEN ', [' + KCU.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;		')' + CHAR(13)+CHAR(10)+&lt;br&gt;		'WITH FILLFACTOR = ' + &lt;br&gt;			CONVERT(varchar(3), --Replace 0 FILLFACTOR with 100&lt;br&gt;				ISNULL(NULLIF(&lt;br&gt;					INDEXPROPERTY(OBJECT_ID(TC.TABLE_NAME), TC.CONSTRAINT_NAME, 'IndexFillFactor'), &lt;br&gt;				0), 100)&lt;br&gt;				) + CHAR(13)+CHAR(10)+&lt;br&gt;		'ON [' + sfg.groupname + ']' + CHAR(13)+CHAR(10)+CHAR(13)+CHAR(10)&lt;br&gt;		AS [-- Create PK/UNIQUE Constraints]&lt;br&gt;	FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC&lt;br&gt;	JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU ON TC.TABLE_NAME = KCU.TABLE_NAME&lt;br&gt;			AND TC.CONSTRAINT_NAME = KCU.CONSTRAINT_NAME&lt;br&gt;	LEFT JOIN sysindexes s ON s.name = TC.CONSTRAINT_NAME&lt;br&gt;		AND s.id = object_id(TC.TABLE_NAME)&lt;br&gt;	LEFT JOIN sysfilegroups sfg ON sfg.groupid = s.groupid&lt;br&gt;	WHERE TC.CONSTRAINT_TYPE IN ('PRIMARY KEY', 'UNIQUE')&lt;br&gt;		AND TC.TABLE_NAME = @TableName&lt;br&gt;	GROUP BY &lt;br&gt;		TC.TABLE_SCHEMA,&lt;br&gt;		TC.TABLE_NAME,&lt;br&gt;		TC.CONSTRAINT_NAME,&lt;br&gt;		TC.CONSTRAINT_TYPE,&lt;br&gt;		sfg.groupname&lt;br&gt;	ORDER BY INDEXPROPERTY(OBJECT_ID(TC.TABLE_NAME), TC.CONSTRAINT_NAME, 'IsClustered') DESC&lt;br&gt;	&lt;br&gt;	SELECT 'ALTER TABLE [' + FC.TABLE_SCHEMA + '].[' + FC.TABLE_NAME + ']' + CHAR(13)+CHAR(10)+ &lt;br&gt;		'ADD CONSTRAINT [' + FC.CONSTRAINT_NAME + '] ' + FC.CONSTRAINT_TYPE + CHAR(13)+CHAR(10)+&lt;br&gt;		'  (' +&lt;br&gt;			MAX(CASE KCU_FK.ORDINAL_POSITION WHEN 1 THEN '[' + KCU_FK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_FK.ORDINAL_POSITION WHEN 2 THEN ', [' + KCU_FK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_FK.ORDINAL_POSITION WHEN 3 THEN ', [' + KCU_FK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_FK.ORDINAL_POSITION WHEN 4 THEN ', [' + KCU_FK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_FK.ORDINAL_POSITION WHEN 5 THEN ', [' + KCU_FK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_FK.ORDINAL_POSITION WHEN 6 THEN ', [' + KCU_FK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_FK.ORDINAL_POSITION WHEN 7 THEN ', [' + KCU_FK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_FK.ORDINAL_POSITION WHEN 8 THEN ', [' + KCU_FK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_FK.ORDINAL_POSITION WHEN 9 THEN ', [' + KCU_FK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_FK.ORDINAL_POSITION WHEN 10 THEN ', [' + KCU_FK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_FK.ORDINAL_POSITION WHEN 11 THEN ', [' + KCU_FK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_FK.ORDINAL_POSITION WHEN 12 THEN ', [' + KCU_FK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_FK.ORDINAL_POSITION WHEN 13 THEN ', [' + KCU_FK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_FK.ORDINAL_POSITION WHEN 14 THEN ', [' + KCU_FK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_FK.ORDINAL_POSITION WHEN 15 THEN ', [' + KCU_FK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_FK.ORDINAL_POSITION WHEN 16 THEN ', [' + KCU_FK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;		')' + CHAR(13)+CHAR(10)+&lt;br&gt;		'REFERENCES [' + TC.TABLE_SCHEMA + '].[' + TC.TABLE_NAME + ']' + CHAR(13)+CHAR(10)+&lt;br&gt;		'  (' +&lt;br&gt;			MAX(CASE KCU_PK.ORDINAL_POSITION WHEN 1 THEN '[' + KCU_PK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_PK.ORDINAL_POSITION WHEN 2 THEN ', [' + KCU_PK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_PK.ORDINAL_POSITION WHEN 3 THEN ', [' + KCU_PK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_PK.ORDINAL_POSITION WHEN 4 THEN ', [' + KCU_PK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_PK.ORDINAL_POSITION WHEN 5 THEN ', [' + KCU_PK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_PK.ORDINAL_POSITION WHEN 6 THEN ', [' + KCU_PK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_PK.ORDINAL_POSITION WHEN 7 THEN ', [' + KCU_PK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_PK.ORDINAL_POSITION WHEN 8 THEN ', [' + KCU_PK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_PK.ORDINAL_POSITION WHEN 9 THEN ', [' + KCU_PK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_PK.ORDINAL_POSITION WHEN 10 THEN ', [' + KCU_PK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_PK.ORDINAL_POSITION WHEN 11 THEN ', [' + KCU_PK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_PK.ORDINAL_POSITION WHEN 12 THEN ', [' + KCU_PK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_PK.ORDINAL_POSITION WHEN 13 THEN ', [' + KCU_PK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_PK.ORDINAL_POSITION WHEN 14 THEN ', [' + KCU_PK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_PK.ORDINAL_POSITION WHEN 15 THEN ', [' + KCU_PK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;			MAX(CASE KCU_PK.ORDINAL_POSITION WHEN 16 THEN ', [' + KCU_PK.COLUMN_NAME + ']' ELSE '' END) +&lt;br&gt;		')' + CHAR(13)+CHAR(10)+CHAR(13)+CHAR(10)&lt;br&gt;		AS [-- Create FK Constraints]&lt;br&gt;	FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC&lt;br&gt;	JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU_PK ON TC.TABLE_NAME = KCU_PK.TABLE_NAME&lt;br&gt;			AND TC.CONSTRAINT_NAME = KCU_PK.CONSTRAINT_NAME&lt;br&gt;	JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC ON RC.UNIQUE_CONSTRAINT_NAME = TC.CONSTRAINT_NAME&lt;br&gt;	JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS FC ON FC.CONSTRAINT_NAME = RC.CONSTRAINT_NAME&lt;br&gt;	JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU_FK ON FC.TABLE_NAME = KCU_FK.TABLE_NAME&lt;br&gt;			AND FC.CONSTRAINT_NAME = KCU_FK.CONSTRAINT_NAME&lt;br&gt;	WHERE TC.CONSTRAINT_TYPE IN ('PRIMARY KEY', 'UNIQUE')&lt;br&gt;		AND TC.TABLE_NAME = @TableName&lt;br&gt;	GROUP BY &lt;br&gt;		FC.TABLE_SCHEMA,&lt;br&gt;		FC.TABLE_NAME,&lt;br&gt;		FC.CONSTRAINT_NAME,&lt;br&gt;		FC.CONSTRAINT_TYPE,&lt;br&gt;		TC.TABLE_SCHEMA,&lt;br&gt;		TC.TABLE_NAME&lt;br&gt;END&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
Let me know if this helps you or if there's some modification that should be made... Enjoy!&lt;/p&gt;&lt;br&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=90" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/Scripts/default.aspx">Scripts</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/T-SQL/default.aspx">T-SQL</category></item><item><title>Pattern-based split string</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2006/07/12/pattern-based-split-string.aspx</link><pubDate>Thu, 13 Jul 2006 01:31:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:89</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>9</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/89.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=89</wfw:commentRss><description>"hickymanz" asked in the &lt;a href="http://www.sqlservercentral.com/forums/shwmessage.aspx?forumid=8&amp;amp;messageid=161089" target="#"&gt;SQL Server Central forums&lt;/a&gt;
for a method of counting unique words in a text column. Wayne Lawton
recommended using a string split function, which was a good idea, but
not quite adequate for the job in my opinion. &lt;p&gt;
Typical string split functions, like &lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/splitting-a-string-of-unlimited-length.aspx"&gt;this one that I wrote&lt;/a&gt;
can handle only a single delimiter, e.g. a comma. But in the case of
splitting for unique words you want all sorts of possible delimiters --
punctuation marks, white space including spaces, possibly numerics,
etc.
&lt;/p&gt;&lt;p&gt;I believe I've seen other, similar requests for getting unique
words before, so I decided to solve the problem. I modified that split
string function to accept a pattern of &lt;i&gt;valid characters&lt;/i&gt;
for the second argument. Anything NOT in the pattern will be treated as
a delimiter. I figured that would be a bit more flexible than the other
way around, so that people don't have to type in (or figure out) every
single possible white space and/or punctuation character.
&lt;/p&gt;&lt;p&gt;Anyway, I think the modification is pretty straightforward if
you understand the previous function, so check it out and if you have
any questions feel free to ask. Here is the function:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;CREATE FUNCTION dbo.SplitStringPattern&lt;br&gt;(&lt;br&gt;	@List TEXT,&lt;br&gt;	@Pattern VARCHAR(50)&lt;br&gt;)&lt;br&gt;RETURNS @ReturnTbl TABLE (OutParam VARCHAR(20))&lt;br&gt;WITH SCHEMABINDING&lt;br&gt;AS&lt;br&gt;BEGIN&lt;br&gt;	DECLARE @LeftSplit VARCHAR(7998)&lt;br&gt;	DECLARE @SplitStart INT SET @SplitStart = 0&lt;br&gt;	DECLARE @SplitEnd INT&lt;br&gt;	SET @SplitEnd = 7998&lt;br&gt;&lt;br&gt;	SET @Pattern = '%' + '[^' + RIGHT(@Pattern, LEN(@Pattern) - 1) + '%'&lt;br&gt;&lt;br&gt;	DECLARE @Delimiter CHAR(1)&lt;br&gt;	SELECT @Delimiter = CHAR(MAX(Number))&lt;br&gt;	FROM dbo.Numbers&lt;br&gt;	WHERE CHAR(Number) LIKE @Pattern&lt;br&gt;&lt;br&gt;	SELECT @SplitEnd = MAX(Number)&lt;br&gt;	FROM dbo.Numbers&lt;br&gt;	WHERE (SUBSTRING(@List, Number, 1) LIKE @Pattern&lt;br&gt;			OR Number = DATALENGTH(@List) + 1)&lt;br&gt;		AND Number BETWEEN @SplitStart AND @SplitEnd&lt;br&gt;&lt;br&gt;	WHILE @SplitStart &amp;lt; DATALENGTH(@List) - 1&lt;br&gt;	BEGIN&lt;br&gt;		SET @LeftSplit = @Delimiter + SUBSTRING(@List, @SplitStart, @SplitEnd - @SplitStart) + @Delimiter&lt;br&gt;&lt;br&gt;		INSERT @ReturnTbl (OutParam)&lt;br&gt;		SELECT LTRIM(RTRIM(SUBSTRING(@LeftSplit, Number + 1,&lt;br&gt;	                    PATINDEX(@Pattern, SUBSTRING(@LeftSplit, Number + 1, LEN(@LeftSplit))) - 1)))&lt;br&gt;	                AS Value&lt;br&gt;		FROM   dbo.Numbers&lt;br&gt;		WHERE  Number &amp;lt;= LEN(@LeftSplit) - 1&lt;br&gt;			AND SUBSTRING(@LeftSplit, Number, 1) LIKE @Pattern&lt;br&gt;			AND SUBSTRING(@LeftSplit, Number + 1,&lt;br&gt;	                    	PATINDEX(@Pattern, SUBSTRING(@LeftSplit, Number + 1, LEN(@LeftSplit))) - 1) &amp;lt;&amp;gt; ''&lt;br&gt;&lt;br&gt;		SET @SplitStart = @SplitEnd + 1&lt;br&gt;		SET @SplitEnd = @SplitEnd + 7998&lt;br&gt;&lt;br&gt;		SELECT @SplitEnd = MAX(Number) + @SplitStart&lt;br&gt;		FROM dbo.Numbers&lt;br&gt;		WHERE (SUBSTRING(@List, Number + @SplitStart, 1) LIKE @Pattern&lt;br&gt;				OR Number+@SplitStart = DATALENGTH(@List) + 1)&lt;br&gt;			AND Number BETWEEN 1 AND @SplitEnd - @SplitStart&lt;br&gt;	END&lt;br&gt;&lt;br&gt;	RETURN&lt;br&gt;END&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;A note on the pattern for input: The pattern should be
single-character based, and must be delimited with [ ]. Also, you must
type in the actual characters in the pattern. I do not recommend using
ranges; ranges cause strange side-effects because they tend to include
(depending on collation) characters that you wouldn't expect to show up
(e.g. characters with umlauts over them). So unless that's what you
really want, don't use ranges.
&lt;/p&gt;&lt;p&gt;
Here's a usage example:
&lt;/p&gt;&lt;pre class="code"&gt;SELECT OutParam&lt;br&gt;FROM dbo.SplitStringPattern('This is a test.', '[abcdefghijklmnopqrstuvwxyz]')&lt;br&gt;&lt;br&gt;&lt;br&gt;OutParam             &lt;br&gt;-------------&lt;br&gt;This&lt;br&gt;is&lt;br&gt;a&lt;br&gt;test&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
Enjoy!&lt;/p&gt;&lt;br&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=89" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/Scripts/default.aspx">Scripts</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/T-SQL/default.aspx">T-SQL</category></item><item><title>Validate a URL from SQL Server</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2006/07/12/validate-a-url-from-sql-server.aspx</link><pubDate>Thu, 13 Jul 2006 01:23:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:83</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>21</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/83.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=83</wfw:commentRss><description>File this one in your folder of things you should probably never use -- but maybe, some day, in an emergency, you'll need this.
&lt;p&gt;
I see posts requesting this functionality all the time. "How do I
validate a URL in SQL Server?" Not just the string, but the URL itself
-- how can we find out if it's valid?
&lt;/p&gt;&lt;p&gt;
Thanks to the Object Automation extended stored procedures Microsoft includes with SQL Server, it's quite easy...
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;CREATE FUNCTION dbo.ValidateURL(@URL VARCHAR(300))&lt;br&gt;RETURNS BIT&lt;br&gt;AS&lt;br&gt;BEGIN&lt;br&gt;	DECLARE @Object INT&lt;br&gt;	DECLARE @Return TINYINT&lt;br&gt;	DECLARE @Valid BIT SET @Valid = 0 --default to false&lt;br&gt;	&lt;br&gt;	--create the XMLHTTP object&lt;br&gt;	EXEC @Return = sp_oacreate 'MSXML2.ServerXMLHTTP.3.0', @Object OUTPUT&lt;br&gt;	IF @Return = 0&lt;br&gt;	BEGIN&lt;br&gt;		DECLARE @Method VARCHAR(350)&lt;br&gt;&lt;br&gt;		--define setTimeouts method&lt;br&gt;		--Resolve, Connect, Send, Receive&lt;br&gt;		SET @Method = 'setTimeouts(45000, 45000, 45000, 45000)'&lt;br&gt;&lt;br&gt;		--set the timeouts&lt;br&gt;		EXEC @Return = sp_oamethod @Object, @Method&lt;br&gt;&lt;br&gt;		IF @Return = 0&lt;br&gt;		BEGIN&lt;br&gt;			--define open method&lt;br&gt;			SET @Method = 'open("GET", "' + @URL + '", false)'&lt;br&gt;	&lt;br&gt;			--Open the connection&lt;br&gt;			EXEC @Return = sp_oamethod @Object, @Method&lt;br&gt;		END&lt;br&gt;	&lt;br&gt;		IF @Return = 0&lt;br&gt;		BEGIN&lt;br&gt;			--SEND the request&lt;br&gt;			EXEC @Return = sp_oamethod @Object, 'send()'&lt;br&gt;		END&lt;br&gt;	&lt;br&gt;		IF @Return = 0&lt;br&gt;		BEGIN&lt;br&gt;			DECLARE @Output INT&lt;br&gt;			EXEC @Return = sp_oamethod @Object, 'status', @Output OUTPUT&lt;br&gt;	&lt;br&gt;			IF @Output = 200&lt;br&gt;			BEGIN&lt;br&gt;				SET @Valid = 1&lt;br&gt;			END&lt;br&gt;		END&lt;br&gt;	END&lt;br&gt;	&lt;br&gt;	--destroy the object&lt;br&gt;	EXEC sp_oadestroy @Object&lt;br&gt;&lt;br&gt;	RETURN (@Valid)&lt;br&gt;END&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
... And that is it ...
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;SELECT dbo.ValidateURL('http://www.microsoft.com/sql')&lt;br&gt;&lt;br&gt;---&lt;br&gt;1&lt;br&gt;&lt;br&gt;&lt;br&gt;SELECT dbo.ValidateURL('http://www.XMLisNOTaMAGICbullet.com/')&lt;br&gt;&lt;br&gt;---&lt;br&gt;0&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;Note, you don't want to run this thing against a big table. It runs
synchronously and waits for the remote site to respond. That can
definitely hold locks open a lot longer than you might want.
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;hr&gt;
&lt;p&gt;
&lt;b&gt;UPDATE: &lt;/b&gt; Thanks to &lt;a href="http://blog.marcustucker.com/" target="#"&gt;Marcus Tucker&lt;/a&gt;
for pointing out that Microsoft.XMLHTTP (the XMLHTTP client object) was
not the right choice here. I've updated the UDF to use
MSXML2.ServerXMLHTTP.3.0, the XMLHTTP server object, instead.
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;hr&gt;
&lt;p&gt;
&lt;b&gt;ANOTHER UPDATE: &lt;/b&gt; Added a call to the setTimeouts method, as I
discovered that this wasn't behaving the same on differente servers --
apparently there is some default timeout set somewhere; I have no idea
where, though. Anyway, the four timeout types have all been set to 45
seconds (45000 ms). Tweak them if you need to.&lt;/p&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=83" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/Scripts/default.aspx">Scripts</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/T-SQL/default.aspx">T-SQL</category></item></channel></rss>