{"id":239,"date":"2007-03-16T00:00:00","date_gmt":"2007-03-16T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/the-helper-table-workbench\/"},"modified":"2021-09-29T16:22:21","modified_gmt":"2021-09-29T16:22:21","slug":"the-helper-table-workbench","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/the-helper-table-workbench\/","title":{"rendered":"The Helper Table Workbench"},"content":{"rendered":"<p>Sometimes, when writing TSQL code in functions or procedures, it is tempting to do iterations, or even worse, a cursor, when it isn&#8217;t really necessary. Cursors and iterations are both renowned for slowing down Transact SQL code. SQL Server just isn&#8217;t designed for it.<\/p>\n<p>However, there is usually a way to do such operations in a set-based way. If you do so, then your routines will run a lot faster, with speed at least doubling. There are a lot of tricks to turning a problem that seems to require an iterative approach into a set-based operation, and we wish we could claim we&#8217;d invented one of them. Probably the most useful technique involves that apparently useless entity, the &#8216;helper&#8217; table. This workshop will concentrate on this, because it is probably the most widely used.<\/p>\n<p>The most common Helper table you&#8217;ll see is a table with nothing but the numbers in a sequence from 1 upwards. These tables have a surprising number of uses. Once you&#8217;ve understood the principles behind helper tables, then you&#8217;ll think of many more. We&#8217;ll be providing several examples where a helper table suddenly makes life easier. The objective is to show the principles so that you&#8217;ll try out something similar the next time you have to tackle a tricky operation in TSQL. As always, you&#8217;re encouraged to load the <a href=\"http:\/\/www.simple-talk.com\/content\/file.ashx?file=430\">example script<\/a> into Query Analyser or Management Studio, and experiment!<\/p>\n<h2>Our examples include:<\/h2>\n<p><a href=\"#first\">Splitting Strings into table-rows, based on a specified delimiter<\/a><br \/>\n <a href=\"#second\">Encoding and decoding a string<\/a><br \/>\n <a href=\"#third\">Substituting values into a string<\/a><br \/>\n <a href=\"#fourth\">Extracting individual words from a string into a table<\/a><br \/>\n <a href=\"#fifth\">Extracting all the numbers in a string into a table<\/a><br \/>\n <a href=\"#sixth\">Removing all text between delimiters<\/a><br \/>\n <a href=\"#seventh\">Scrabble score<\/a><br \/>\n <a href=\"#eighth\">Moving averages<\/a><br \/>\n <a href=\"#nineth\">Getting the &#8216;Week beginning&#8217; date in a table<\/a><br \/>\n <a href=\"#tenth\">Calculating the number of working days between dates.<\/a><\/p>\n<p>Note. These examples use <code>VARCHAR(8000)<\/code> just so they compile on both SQL Server 2000 and 2005. If you are using SQL Server 2005, you&#8217;ll probably want to change them to <code>VARCHAR(MAX)<\/code>.<\/p>\n<p>Before we start, we&#8217;ll need a helper table of numbers. Our examples aren&#8217;t going to require high numbers, but we&#8217;ve parameterised the size of the table that the routine creates<\/p>\n<h2>Creating the helper table<\/h2>\n<p>Here is a routine that checks to see if such a &#8216;helper&#8217; table called <code>numbers<\/code> exists, and, if not, creates it.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE PROCEDURE spMaybeBuildNumberTable \r\n@size INT=10000 \r\nAS \r\nBEGIN \r\nSET NOCOUNT ON \r\nIF NOT EXISTS (SELECT * FROM dbo.sysobjects \r\n  WHERE id = OBJECT_ID(N'[dbo].[Numbers]') \r\n   AND OBJECTPROPERTY(id, N'IsUserTable') = 1) \r\n    BEGIN \r\n    CREATE TABLE [dbo].[Numbers]( \r\n     [number] [int], \r\n    CONSTRAINT [Index_Numbers] PRIMARY KEY CLUSTERED \r\n    ( \r\n     [number] ASC \r\n    ) ON [PRIMARY] \r\n    ) ON [PRIMARY] \r\n\r\n    DECLARE @ii INT \r\n    SELECT @ii=1 \r\n    WHILE (@ii&lt;=@size) \r\n     BEGIN \r\n     INSERT INTO NUMBERS(NUMBER) SELECT @II \r\n     SELECT @II=@II+1 \r\n     END \r\n    END \r\nEND \r\n<\/pre>\n<p>Once you have one of these tables, which we&#8217;ve seen described as the Transact SQL developer&#8217;s &#8216;Swiss Army Knife&#8217;, you will not want to be without it.<\/p>\n<h2 id=\"first\">Splitting Strings into table-rows, based on a specified delimiter<\/h2>\n<p>Imagine you have a string which you want to break into words to make into a table.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">DECLARE @ordinal VARCHAR(255) \r\nSELECT @Ordinal= \r\n'first second third fourth fifth sixth seventh eighth ninth tenth' \r\n<\/pre>\n<p>This can be done very simply and quickly through the following single SQL Select statement:<\/p>\n<p>(Make sure you have executed the <code>spMaybeBuildNumberTable<\/code> procedure first!)<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SELECT SUBSTRING(@Ordinal+' ', number, \r\n    CHARINDEX(' ', @Ordinal+' ', number) - number) \r\nFROM Numbers \r\nWHERE number &lt;= LEN(@Ordinal) \r\nAND SUBSTRING(' ' + @Ordinal, \r\n            number, 1) = ' ' \r\nORDER BY number RETURN \r\n\r\n\/*----with the result \r\nfirst \r\nsecond \r\nthird \r\nfourth \r\nfifth \r\nsixth \r\nseventh \r\neighth \r\nnineth \r\ntenth \r\n\r\n(10 row(s) affected) \r\n<\/pre>\n<p>You can then enshrine this principle into a table function that will take any delimiter to split a string. (We believe that the credit for this clever routine should go to Anith Sen).<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE FUNCTION [dbo].[uftSplitString] \r\n( \r\n@String VARCHAR(8000), \r\n@Delimiter VARCHAR(255) \r\n) \r\nRETURNS \r\n@Results TABLE \r\n( \r\nSeqNo INT IDENTITY(1, 1), \r\nItem VARCHAR(8000) \r\n) \r\nAS \r\nBEGIN \r\nINSERT INTO @Results (Item) \r\nSELECT SUBSTRING(@String+@Delimiter, number, \r\n    CHARINDEX(@Delimiter, @String+@Delimiter, number) - number) \r\nFROM Numbers \r\nWHERE number &lt;= LEN(REPLACE(@String,' ','|')) \r\nAND SUBSTRING(@Delimiter + @String, \r\n            number, \r\n            LEN(REPLACE(@delimiter,' ','|'))) = @Delimiter \r\nORDER BY number RETURN \r\nEND \r\n<\/pre>\n<p>This is the fastest means I have come across in TSQL to split a string into its components as rows. Try this, which give you a table with the integer and the nominal name.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SELECT * FROM dbo.uftSplitString( \r\n    'one,two,three,four,five,six,seven,eight,nine,ten',',') \r\n--or this, which \r\nSELECT * FROM dbo.uftSplitString( \r\n    'Monday--Tuesday--Wednesday--thursday--friday--saturday--sunday','--') \r\n\r\n    <\/pre>\n<h2 id=\"second\">Encoding and decoding a string<\/h2>\n<p>You can use the same principle for a lot of string operations.<\/p>\n<p>Here is one that will <code>URLencode<\/code> a string so it can be used in a <code>POST<\/code> or <code>GET HTTP<\/code> operation. The string is converted into a table with one character per row and, after being operated on, it is re-assembled.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE FUNCTION ufsURLEncoded \r\n( \r\n@String VARCHAR(MAX) \r\n) \r\nRETURNS VARCHAR(MAX) \r\nBEGIN \r\nDECLARE @URLEncodedString VARCHAR(MAX) \r\nSELECT @URLEncodedString='' \r\n\r\nSELECT @URLEncodedString=@URLEncodedString+ \r\n    CASE WHEN theChar LIKE '[A-Za-z0-9()''*-._!]' \r\n    THEN theChar \r\n    ELSE '%' \r\n            + SUBSTRING ('0123456789ABCDEF', \r\n            (ASCII(theChar) \/ 16)+1,1) \r\n            + SUBSTRING ('0123456789ABCDEF', \r\n            (ASCII(theChar) % 16)+1,1) \r\n    END \r\nFROM \r\n    ( \r\n    SELECT [theChar]=SUBSTRING(@string,number,1) \r\n    FROM numbers \r\n    WHERE number &lt;= LEN(@String) ) Characterarray \r\n    -- Return the result of the function \r\n    RETURN @URLEncodedString \r\n\r\nEND \r\n<\/pre>\n<p>This sort of routine is a lot less complex than the iterative methods and is, as one would expect, a lot faster. Just to show that this is no fluke, here is the reverse function to decode a URL Query string.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE FUNCTION ufsURLDecoded \r\n( \r\n@String VARCHAR(MAX) \r\n) \r\nRETURNS VARCHAR(MAX) \r\nBEGIN \r\nSELECT @string= \r\n    REPLACE(@string,escapeString, TheCharacter) \r\nFROM \r\n    (SELECT \r\n        [escapeString]=SUBSTRING(@string,number,3), \r\n        [theCharacter]=CHAR( \r\n                (CHARINDEX( \r\n                    SUBSTRING(@string,number+1, \r\n                    1), \r\n                '0123456789ABCDEF')-1)*16 \r\n                +CHARINDEX( \r\n                    SUBSTRING(@string,number+2, \r\n                    1), \r\n                '0123456789ABCDEF')-1) \r\n\r\n    FROM numbers \r\n    WHERE number &lt;= LEN(@String) \r\n        AND SUBSTRING(@string,number,1) = '%' \r\n    )f \r\nWHERE CHARINDEX(escapeString,@string)&gt;0 \r\nRETURN @String \r\nEND \r\n<\/pre>\n<p>Here is a simple function that acts as an int to hex converter.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE FUNCTION ufsIntToHex \r\n( \r\n    @integer INT \r\n) \r\nRETURNS VARCHAR(20) \r\nAS \r\nBEGIN \r\nDECLARE @result VARCHAR(80) \r\nSELECT @result='' \r\n\r\nIF @integer&gt;0 \r\n    SELECT @result=CASE WHEN @integer&gt;0 THEN \r\n            SUBSTRING('0123456789ABCDEF',(@integer%16)+1,1) \r\n            ELSE '' END+@result, \r\n     @integer=@integer\/16 \r\n    FROM numbers WHERE number&lt;20 \r\nELSE SELECT @result=0 \r\nRETURN '0x'+@result \r\n\r\nEND \r\n<\/pre>\n<p>It works this way:<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SELECT dbo.ufsIntToHex(1024) \r\n<\/pre>\n<p>And what about TSQL that converts hex to int?<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE FUNCTION ufiHexToInt \r\n( \r\n    @HexString VARCHAR(50) \r\n) \r\nRETURNS INT \r\nAS \r\nBEGIN \r\nDECLARE @result INT \r\nSELECT @result=0 \r\nSELECT @Result=(@result*16)+ \r\n    (CHARINDEX(SUBSTRING(@HexString,number,1),'0123456789ABCDEF')-1) \r\n    FROM numbers WHERE number&lt;=LEN(@HexString) \r\n    AND SUBSTRING(@HexString,number,1) LIKE '[0-9ABCDEF]' \r\nRETURN @Result \r\n\r\nEND \r\n<\/pre>\n<p>And now we can test it out:<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SELECT COUNT(*) \r\n    FROM numbers \r\nWHERE dbo.ufiHexToInt(dbo.ufsIntToHex(number))&lt;&gt;number \r\n<\/pre>\n<h2 id=\"third\">Substituting values into a string<\/h2>\n<p>Next, we have a function that uses these principles to do macro substitution of values into a string. It will work for replacing XHTML placeholders with a value, or producing error messages in a variety of languages. In fact, uses keep popping up for this sort of function. In this version, one can specify what strings are used for substitutions (the default is %1, %2, %3 etc,) and what you use as the delimiter of your list of macros and values.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE FUNCTION [dbo].[ufsSubstitute] \r\n( \r\n@Template VARCHAR(8000), \r\n@macroList VARCHAR(8000), \r\n@valueList VARCHAR(8000), \r\n@Delimiter VARCHAR(255) \r\n) \r\nRETURNS \r\nVARCHAR(8000) \r\nAS \r\nBEGIN \r\nDECLARE @macros TABLE (MyID INT IDENTITY(1,1),variable VARCHAR(80)) \r\nDECLARE @values TABLE (MyID INT IDENTITY(1,1),[value] VARCHAR(8000)) \r\n--get all the variables \r\nINSERT INTO @macros (variable) \r\n    SELECT SUBSTRING(@MacroList+@Delimiter, number, \r\n        CHARINDEX(@Delimiter, @MacroList+@Delimiter, number) - number) \r\nFROM Numbers \r\nWHERE number &lt;= LEN(@MacroList) \r\n    AND SUBSTRING(@Delimiter + @MacroList, number, LEN(@delimiter)) \r\n        = @Delimiter \r\nORDER BY number \r\n\r\nINSERT INTO @values ([value]) \r\n    SELECT SUBSTRING(@ValueList+@Delimiter, number, \r\n        CHARINDEX(@Delimiter, @ValueList+@Delimiter, number) - number) \r\nFROM Numbers \r\nWHERE number &lt;= LEN(@ValueList) \r\nAND SUBSTRING(@Delimiter + @ValueList, number, \r\n        LEN(@delimiter)) = @Delimiter \r\nORDER BY number \r\n\r\nSELECT @Template= \r\n    REPLACE(@Template,COALESCE(variable, \r\n                '%'+CAST(v.MyID AS VARCHAR)), \r\n                [value]) \r\nFROM @values v \r\nLEFT OUTER JOIN @macros m ON v.MyID=m.MyID \r\n    WHERE CHARINDEX(COALESCE(variable,'%'+CAST(v.MyID AS VARCHAR)) \r\n        ,@Template)&gt;0 \r\n\r\nRETURN (@Template) \r\n\r\nEND \r\n<\/pre>\n<p>There are several ways that we can use this routine in practical applications. Try out these and see what happens!<\/p>\n<pre class=\"lang:tsql theme:ssms2012\"> \r\nSELECT dbo.ufsSubstitute (NULL,NULL,'',',') \r\nSELECT dbo.ufsSubstitute ('','','',',') \r\nSELECT dbo.ufsSubstitute ( \r\n    ' views','','6',',') \r\nSELECT dbo.ufsSubstitute (' \r\nDear $1 $2, \r\nIt has come to our attention that your $3 account is $4 \r\nto the extent of \u00c2\u00a3$5. \r\nPlease phone our adviser, $6 $7 on $8 who will inform you of \r\nthe various actions that need to be taken', \r\n    '$1,$2,$3,$4,$5,$6,$7,$8', \r\n    'Mrs,Prism,current,overdrawn,5678,Mr,Grabbitas,04585 725938', \r\n    ',') \r\nSELECT dbo.ufsSubstitute ('To @Destination; \r\nReport dated @Date \r\nThe @table table is now @rows long. please @action' \r\n,'@Destination|@Date|@Table|@rows|@action', \r\n'Phil Factor|12 Apr 2007|Log|1273980|truncate it at once','|') \r\nSELECT dbo.ufsSubstitute ( \r\n'I thought that your present of a %1 was %2. Thank you very much. \r\nThe %1 will come in handy for %3' \r\n    ,'' \r\n    ,'trowel|absolutely wonderful|gardening','|') \r\n<\/pre>\n<h2 id=\"fourth\">Extracting individual words from a string into a table<\/h2>\n<p>One can do rather cleverer things than this. For example, one can extract all the words from a string into a table, a row for each<br \/>\n word.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE FUNCTION [dbo].[uftWords] \r\n( \r\n@String VARCHAR(8000) \r\n) \r\nRETURNS \r\n@Results TABLE \r\n( \r\nSeqNo INT IDENTITY(1, 1), \r\nWord VARCHAR(8000) \r\n) \r\nAS \r\nBEGIN \r\nINSERT INTO @Results(word) \r\n    SELECT [word]=LEFT(RIGHT(@string,number), \r\n        PATINDEX('%[^a-z]%',RIGHT(@string,number)+' ')-1) \r\nFROM Numbers \r\nWHERE number &lt;= LEN(@String) \r\nAND PATINDEX('%[a-z]%',RIGHT(@string,number))=1 \r\nAND PATINDEX('%[^a-z]%',RIGHT(' '+@string,number+1))=1 \r\nORDER BY number DESC \r\nRETURN \r\nEND \r\n--and you can get the words (we use it for inversion indexes) \r\nSELECT * FROM dbo.uftWords ('One can do rather cleverer \r\nthings than this. &lt;&gt;!  For example, one can extract all the \r\nwords from a string into a table, a row for each word.') \r\n--or a word count \r\nSELECT COUNT(*) FROM dbo.uftWords ('It is extraordinary \r\nhow easy   it is to get a wordcount using this ') \r\n<\/pre>\n<h2 id=\"fifth\">Extracting all the numbers in a string into a table<\/h2>\n<p>Even more useful than this is a function that picks out all the numbers from a string into a table. You can therefore easily pick out the third or fourth string simply, because the table has the order as well as the number itself. Were it not for the unary minus operator, this would have been a delightfully simple function.<\/p>\n<p>If you are using this routine, you&#8217;ll want to cast these numbers into the number type of your choice. We supply them as strings.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE FUNCTION [dbo].[uftNumbers] \r\n( \r\n@String VARCHAR(8000) \r\n) \r\nRETURNS \r\n@Results TABLE \r\n( \r\nSeqNo INT IDENTITY(1, 1), \r\nnumber VARCHAR(100) \r\n) \r\nAS \r\nBEGIN \r\nINSERT INTO @Results(number) \r\n    SELECT \r\n    CASE LEFT(RIGHT(' '+@String,number),1) \r\n        WHEN '-' THEN '-' ELSE '' END+ \r\n    SUBSTRING( RIGHT(@String,number-1),1, \r\n        PATINDEX('%[^0-9.]%', \r\n        RIGHT(' '+@String,number-1)+' ')-1) \r\n    FROM Numbers \r\n    WHERE number &lt;= LEN(REPLACE(@String,' ','!'))+1 \r\n        AND PATINDEX('%[^0-9.][0-9]%',RIGHT(' ' \r\n        +@String,number))=1 \r\n    ORDER BY number DESC \r\nRETURN \r\nEND \r\n--So we try out a few examples just to see. It removes anything \r\n--that doesn't look kile a number \r\nSELECT * FROM dbo.uftNumbers('there are numbers like 34.56, \r\n-56, 67.878, maybe34; possibly56, and a few others like &lt;789023&gt;') \r\n\r\nSELECT * FROM dbo.uftNumbers('23,87986,56.78,67,09,23,30') \r\nSELECT * FROM dbo.uftNumbers('') \r\nSELECT * FROM dbo.uftNumbers('  \r\n&lt;div&gt;') \r\nSELECT * FROM dbo.uftNumbers('there are numbers like 34.56, \r\n-56, 67.878, maybe34; possibly56, and a few others like &lt;789023&gt;') \r\n\r\n\/* \r\nSeqNo    number \r\n----------- ------------ \r\n1      34.56    \r\n2      -56     \r\n3      67.878    \r\n4      34      \r\n5      56      \r\n6      789023    \r\n\r\n(6 row(s) affected) \r\n*\/ \r\n<\/pre>\n<h2 id=\"sixth\">Removing all text between delimiters<\/h2>\n<p>This is a handy little routine for looking at the strings in HTML code, but seems to earn its keep in a lot of other ways. You specify the opening and closing delimiter. At the moment, only single-character delimiters are allowed. Can anyone re-write it to allow multi-character delimiters?.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE FUNCTION [dbo].[ufsRemoveDelimited] \r\n( \r\n@String VARCHAR(8000), \r\n@OpeningDelimiter CHAR(1), \r\n@ClosingDelimiter CHAR(1) \r\n) \r\nRETURNS \r\nVARCHAR(8000) \r\nAS \r\nBEGIN \r\nDECLARE @newString VARCHAR(8000) \r\nIF @OpeningDelimiter = @ClosingDelimiter \r\n    BEGIN \r\n    RETURN NULL \r\n    END \r\nIF @OpeningDelimiter+@ClosingDelimiter+@String IS NULL \r\n    BEGIN \r\n    RETURN @String \r\n    END \r\nSELECT @NewString='' \r\nSELECT @newString =@newString +SUBSTRING(@String,number,1) \r\n    FROM numbers \r\n    WHERE number&lt;=LEN (REPLACE(@string,' ','|')) \r\n    AND \r\n     CHARINDEX (@OpeningDelimiter,@string+@OpeningDelimiter,number) \r\n        &lt; \r\n     CHARINDEX (@ClosingDelimiter,@string+' '+@closingDelimiter,number) \r\nAND number &lt;&gt; CHARINDEX (@OpeningDelimiter,@string,number) \r\nRETURN @NewString \r\nEND \r\n<\/pre>\n<p>So we can try it out with brackets:<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SELECT dbo.ufsRemoveDelimited( \r\n    'this will appear(but not this),)will this?(','(',')') \r\n<\/pre>\n<p>Or if you want to take out tags:<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SELECT dbo.ufsRemoveDelimited( \r\n    'this will appear  \r\n    &lt;div&gt;and this &lt;\/div&gt;and this','&lt;','&gt;') \r\n--or this \r\nSELECT dbo.ufsRemoveDelimited( \r\n'&lt;?xml version=\"1.0\" encoding=\"us-ascii\"?&gt;\r\n&lt;!DOCTYPE html PUBLIC \"-\/\/W3C\/\/DTD XHTML 1.0 Strict\/\/EN\"\r\n\"http:\/\/www.w3.org\/TR\/xhtml1\/DTD\/xhtml1-strict.dtd\"&gt;\r\n&lt;html xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"&gt;\r\n&lt;head&gt;\r\n&lt;title&gt;A poem&lt;\/title&gt;\r\n&lt;\/head&gt;\r\n&lt;body&gt;\r\n    &lt;div style=\"float left: width:300px;\"&gt;\r\n        &lt;h2&gt;\r\n           Weather forecast\r\n        &lt;\/h2&gt;\r\n        &lt;p&gt;\r\n            The rain it raineth every day&lt;br \/&gt;\r\n            upon the just and unjust fellah&lt;br \/&gt;\r\n            but mainly on the just, because,&lt;br \/&gt;\r\n            the unjust pinched the just''s umbrella&lt;br \/&gt;\r\n        &lt;\/p&gt;\r\n    &lt;\/div&gt;\r\n&lt;\/body&gt;\r\n&lt;\/html&gt;\r\n','&lt;','&gt;')\r\n<\/pre>\n<h2 id=\"seventh\">Scrabble Score<\/h2>\n<p>And as a slightly silly example of the sort of chore that crops up occasionally when analysing strings, character by character, here is a way of scoring a string for Scrabble, assuming it is all on ordinary squares!<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE FUNCTION ufiScrabbleScore \r\n( \r\n    @String VARCHAR(100) \r\n) \r\nRETURNS INT \r\nAS \r\nBEGIN \r\nDECLARE @Total INT \r\nSELECT @total=0 \r\n\r\nSELECT @Total=@total+ \r\nCASE WHEN SUBSTRING(@String,number,1) NOT LIKE '[a-z]' THEN NULL \r\n    ELSE \r\nCAST(SUBSTRING('00000000001122223333347799', \r\n    CHARINDEX(SUBSTRING(@String,number,1), \r\n    'EAIONRTLSUDGBCMPFHVWYKJXQZ') \r\n            ,1) AS INT)+1 END \r\nFROM numbers WHERE number &lt;=LEN(@String) \r\nRETURN @Total \r\nEND \r\n<\/pre>\n<p>And now we try it out!<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SELECT dbo.ufiScrabbleScore('Quiz') --22 \r\nSELECT dbo.ufiScrabbleScore('Robyn') --10 \r\n<\/pre>\n<p>Now we find out which are the highest scorers, assuming an illegal quantity of tiles. We use the WordList from Phil&#8217;s Blog on the Fireside Fun of Decapitations.<\/p>\n<p>Don&#8217;t try running this without the WordList!<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SELECT dbo.ufiScrabbleScore(word),word \r\nFROM wordlist ORDER BY dbo.ufiScrabbleScore(word) DESC \/* \r\n49     razzamatazz \r\n48     razzmatazz \r\n45     pizzazz \r\n43     quizzically \r\n39     squeezeboxes \r\n38     quizzical \r\n38     psychoanalyzing \r\n37     psychoanalyzed \r\n37     squeezebox \r\n..etc.... \r\n<\/pre>\n<p>As well as slicing and dicing strings, once one has one&#8217;s helper table,<br \/>\n suddenly time-interval based reporting becomes much easier.<\/p>\n<p><strong id=\"eighth\">Moving averages<\/strong><\/p>\n<p>How about moving averages? Here is a simple <code>select<\/code> statement that gives you the moving average (over a week) of the number of log entries for every day for a year. This can be adapted to give weighted and exponential moving averages over arbitrary time periods. You use this technique for ironing out &#8216;noise&#8217; from a graph in order to accentuate the underlying trend.<\/p>\n<p>To execute this, you will need a table to try it on.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">DROP TABLE #cb \r\nCREATE TABLE #cb (insertionDate datetime)--quantity \r\n--for once, we need to iterate to shuffle the pack \r\nDECLARE @ii INT \r\nSELECT @ii=0 \r\nWHILE @ii&lt;20000 \r\n    BEGIN \r\n    INSERT INTO #cb(insertionDate) \r\n            SELECT DATEADD(Hour,RAND()*8760,'1 jan 2006') \r\n    SELECT @ii=@ii+1 \r\n    END \r\n--and put an index on it \r\nCREATE CLUSTERED INDEX idxInsertionDate ON #cb(insertionDate) \r\n<\/pre>\n<p>Now we can try out a moving average!<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SELECT start,[running average]=COUNT(*)\/7 \r\n    FROM \r\n    ( \r\n    SELECT [order]=number, \r\n            [start]=DATEADD(DAY,number,'1 Jan 2006'), \r\n            [end]=DATEADD(DAY,number+7,'1 Jan 2006') \r\n    FROM numbers \r\n    WHERE DATEADD(DAY,number,'1 Jan 2006') \r\n        BETWEEN '1 Jan 2006' AND '1 Jan 2007')f \r\n    LEFT OUTER JOIN [#cb] \r\n    ON [#cb].insertionDate BETWEEN f.start AND f.[end] \r\n    GROUP BY start \r\n    ORDER BY start \r\n<\/pre>\n<h2 id=\"nineth\">Getting the &#8216;Week beginning&#8217; date in a table<\/h2>\n<p>Here is a UDF that lists all the Mondays (or whatever you want) between two dates.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE FUNCTION uftDatesOfWeekday \r\n( \r\n    @Weekday VARCHAR(10),   \r\n    @StartDate datetime, \r\n    @EndDate DateTime \r\n) \r\nRETURNS TABLE \r\nAS \r\nRETURN \r\n( \r\nSELECT \r\n            [start]=DATEADD(DAY,number-1,@StartDate) \r\n    FROM numbers \r\n    WHERE DATEADD(DAY,number-1,@StartDate)&lt; @EndDate \r\n    AND DATENAME(dw,DATEADD(DAY,number-1,@StartDate))=@Weekday \r\n) \r\n\/* \r\nAnd you can try it out by finding how many mondays there are between \r\nthe first of January and 1st june this year. *\/ \r\nSELECT * FROM dbo.uftDatesOfWeekday('monday','1 Jan 2007','1 Jun 2007') \r\n<\/pre>\n<h2 id=\"tenth\">Number of Working Days between two dates<\/h2>\n<p>Or, how about a UDF that tells you the number of working days between two dates? (you can alter it if Saturday and Sunday are not your days off!)<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE FUNCTION ufiWorkingDays \r\n( \r\n    @StartDate datetime, \r\n    @EndDate DateTime \r\n) \r\nRETURNS INT \r\nAS \r\nBEGIN \r\nRETURN \r\n(SELECT COUNT(*) \r\n    FROM numbers \r\n    WHERE DATEADD(DAY,number-1,@StartDate)&lt; @EndDate \r\n    AND DATENAME(dw,DATEADD(DAY,number-1,@StartDate)) \r\n    NOT IN ('saturday','sunday')) \r\nEND \r\n<\/pre>\n<p>So how many working days until Christmas?<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SELECT dbo.ufiWorkingDays( \r\n        GETDATE(),'25 Dec '+DATENAME(YEAR,GETDATE())) \r\n        <\/pre>\n<p>We can go on for ever with example of using a numeric Helper table but we won&#8217;t because most of you will have wandered off even before you reach this point. We hope that you&#8217;ll now take over and create some more examples, try them out against iterative solutions that do the same thing. We guarantee you&#8217;ll be pleased with the result!<\/p>\n<div class=\"note\">\n<h3>See also other Workbenches at Simple-Talk<\/h3>\n<p><strong><a href=\"http:\/\/www.simple-talk.com\/sql\/learn-sql-server\/robyn-pages-sql-server-datetime-workbench\/\">Robyn Page&#8217;s SQL Server DATE\/TIME Workbench<\/a>, Robyn Page<\/strong><br \/>\n Date calculation and formatting in SQL Server can be surprisingly tricky. Robyn Page&#8217;s &#8220;hands-on&#8221; workbench will lead you through the minefield.<\/p>\n<p><strong><a href=\"http:\/\/www.simple-talk.com\/sql\/learn-sql-server\/robyn-pages-sql-server-string-manipulation-workbench\/\">Robyn Page&#8217;s SQL Server String Manipulation Workbench<\/a>, Robyn Page<\/strong><br \/>\n String searching and manipulation in SQL Server can be error-prone and tedious&#8230; Unless you&#8217;re armed with the techniques described in Robyn&#8217;s string manipulation workbench&#8230;<\/p>\n<p><strong><a href=\"http:\/\/www.simple-talk.com\/sql\/t-sql-programming\/sql-server-error-handling-workbench\/\">SQL Server Error Handling Workbench<\/a>, Grant Fritchey<\/strong><br \/>\n Grant Fritchey steps into the workbench arena, with an example-fuelled examination of catching and gracefully handling errors in SQL 2000 and 2005, including worked examples of the new <code>TRY..CATCH<\/code> capabilities.<\/p>\n<p><strong><a href=\"http:\/\/www.simple-talk.com\/sql\/learn-sql-server\/robyn-pages-sql-server-cursor-workbench\/\">Robyn Page&#8217;s SQL Server Cursor Workbench<\/a>, Robyn Page<\/strong><br \/>\n The topic of cursors is the ultimate &#8220;hot potato&#8221; in the world of SQL Server. Everyone has a view on when they should and mainly should not be used. By example and testing Robyn Page proves that, when handled with care, cursors are not necessarily a &#8220;bad thing&#8221;.<\/p>\n<p><strong><a href=\"http:\/\/www.simple-talk.com\/sql\/learn-sql-server\/robyn-pages-sql-server-data-validation-workbench\/\">Robyn Page&#8217;s SQL Server Data Validation Workbench<\/a>, Robyn Page<\/strong><br \/>\n Robyn Page provides essential techniques for ensuring the validity of the data being entered into your SQL Server tables.<\/p>\n<p><strong><a href=\"http:\/\/www.simple-talk.com\/sql\/t-sql-programming\/sql-server-excel-workbench\/\">Robyn Page&#8217;s Excel Workbench<\/a>, Robyn Page and Phil Factor<\/strong><br \/>\n The need to produce Excel reports from SQL Server is very common. Here, Robyn Page and Phil Factor present practical techniques for creating and manipulating Excel spreadsheets from SQL Server, using linked servers and T-SQL. The pi\u00e8ce de r\u00e9sistance is a stored procedure that uses OLE Automation&#8230;<\/p>\n<p><strong><a href=\"http:\/\/www.simple-talk.com\/sql\/t-sql-programming\/sql-server-security-workbench-part-1\/\">Robyn Page&#8217;s SQL Server Security Workbench<\/a>, Robyn Page and Phil Factor<\/strong><br \/>\n Robyn Page and Phil Factor present practical T-SQL techniques for controlling access to sensitive information within the database, and preventing malicious SQL injection attacks.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Cursors and iterations are both renowned for slowing down Transact SQL code, but sometimes seem unavoidable. In this workbench, Robyn Page and Phil Factor demonstrate some set-based techniques for string manipulation and time interval-based reporting, which use helper tables rather than the dreaded cursor.&hellip;<\/p>\n","protected":false},"author":221812,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143531],"tags":[4314,4719,4718,4150,4254,4496,4722,4183,4252,4721,4720,4460],"coauthors":[6813,6814],"class_list":["post-239","post","type-post","status-publish","format-standard","hentry","category-t-sql-programming-sql-server","tag-cursors","tag-helper-tables","tag-iteration","tag-sql","tag-stored-procedures","tag-string-manipulation","tag-string-splitting","tag-t-sql","tag-t-sql-programming","tag-time-based-reporting","tag-user-defined-functions","tag-workbench"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/239","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221812"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=239"}],"version-history":[{"count":6,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/239\/revisions"}],"predecessor-version":[{"id":77224,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/239\/revisions\/77224"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=239"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=239"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=239"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=239"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}