• T-SQL代码搜索


      1 SET ANSI_NULLS ON;
      2 SET ANSI_PADDING ON;
      3 SET ANSI_WARNINGS ON;
      4 SET CONCAT_NULL_YIELDS_NULL ON;
      5 SET NUMERIC_ROUNDABORT OFF;
      6 SET QUOTED_IDENTIFIER ON;
      7 
      8 IF OBJECT_ID('[dbo].[FN_SPLIT_INT]') IS NOT NULL
      9     DROP FUNCTION [dbo].[FN_SPLIT_INT]
     10 GO
     11 
     12 IF OBJECT_ID('[dbo].[FN_SPLIT]') IS NOT NULL
     13     DROP FUNCTION [dbo].[FN_SPLIT]
     14 GO
     15 
     16 CREATE FUNCTION [dbo].[FN_SPLIT] (
     17     @Text nvarchar(max),  -- Text to split
     18     @Delimiter nvarchar(1000)   -- Value to split on, try to only pass a single char. See notes for details.
     19 )
     20 /* ------------------------------------------------------------------------------------------------
     21 Purpose: Split text on a common char. 
     22 Design Notes:
     23 1) Will trim leading/trailing white space from items.
     24 2) Will omit blank and null items.
     25 3) Compatible from SQL 2005 and onward (see details about [item_int] in return table)
     26 4) Return table item is nvarchar(max) (not bound by string length)
     27 5) Reasonably durable with escape sequences, so if a delimiter has a [,%,_ in it, the patter should still match.
     28 6) Uses a sliding search window, not saving the remaining text on each iteration.  However, 
     29    saving each item in a temp variable (@item) was faster than using a CTE to temporarily 
     30    store the value, which surprised me.
     31 7) Returns the value as an int as well, which is a common use for this function (splitting comma 
     32    separated lists of ints).  Note that this should be low impact in that if you don't query 
     33    against that column since it is a non-persistent computed column (i.e. low overhead).
     34 8) Supports @Delimiter > 1 char, but slower.  Note in the unit tests, with text of approximately 
     35    10K, 1 char is about 30% faster, hence the big IF block in the code.  Plus, the multi-char 
     36    delimiter does not scale terribly well. The test with 100,000 records, a 1 char delimiter takes 
     37    about 6 seconds while with a 5 char delimiter took 430 seconds (7 minutes!).  As such, try to 
     38    replace your multi char delimiters with a single char value before calling this function.  
     39    Side Note: For what it's worth, I did try an alternative method of saving the remaining 
     40    "working text" as a sub string of text so the search would get faster near the end, but overall 
     41    it was slower at about 500 seconds.
     42 
     43 NOTE: This version does not support case sensitivity.  See "TODO" comments if you need a case insensitive version
     44 
     45 Revision history:           
     46 ---------------------------------------------------------------------------------------------------                           
     47 Date             User                                Change Description                     
     48 ---------------------------------------------------------------------------------------------------
     49 10/10/2013       Brad Joss (bradjoss@hotmail.com)    Initial Public Draft
     50 ---------------------------------------------------------------------------------------------------*/
     51 RETURNS @retTable TABLE 
     52 (
     53     -- Output table definition
     54     [item] nvarchar(max) COLLATE DATABASE_DEFAULT NOT NULL,
     55 
     56     -- Since most of the times we split, we are splitting lists of ints, this makes that process easier.
     57     -- Since the column is non persistent it should only be evaluated when requested (low overhead).
     58     [item_int] as (
     59         -- SQL 2012 version, better, use if possible
     60         -- TRY_CONVERT([int], NULLIF([item],'')) -- SQL 2012 Format, faster and safer, but pre-2012 code provided as well...
     61 
     62         -- Pre SQL 2012 syntax.  Not as reliable, so use 2012 when possible by commenting out this CAST and using the TRY_CONVERT above        
     63         CAST(
     64             CASE 
     65                 WHEN LEN(item) > 11 THEN NULL  -- LEN OF (-2147483648) is 11.  Simple out of bounds checking.
     66                 WHEN ISNUMERIC([item]) = 1 AND [item] NOT LIKE '%.%' THEN [item] -- Ensure value conforms to int
     67                 ELSE null
     68             END 
     69         as int)
     70     )
     71 ) 
     72 WITH SCHEMABINDING
     73 AS
     74 BEGIN 
     75     -- Garbage in, Garbage out.  If they did not pass input data, return nothing.
     76     IF RTRIM(ISNULL(@Text,'')) = '' OR RTRIM(ISNULL(@Delimiter,'')) = ''
     77         RETURN
     78 
     79     DECLARE
     80        @ix bigint -- Current index
     81      , @pix bigint -- Previous index
     82      , @del_len int -- Delimiter length
     83      , @text_len bigint -- Input text length
     84      , @item nvarchar(max) -- Temp item buffer.  I tried w/o using CTEs, but this way was faster
     85 
     86     SELECT @del_len = LEN(@Delimiter)
     87          , @text_len = LEN(@Text)
     88 
     89     IF @del_len = 1
     90     BEGIN -- CHARINDEX MODE (Much faster than PATINDEX mode)
     91         SELECT @ix = CHARINDEX(@Delimiter, @Text) -- TODO: If you want to implment Case Insensitivity here, wrap both in LOWER()
     92              , @pix = 0
     93     
     94         -- No delim found, just return the passed value, trimmed
     95         IF @ix = 0
     96         BEGIN
     97             INSERT INTO @retTable(item) 
     98                 SELECT LTRIM(RTRIM(@Text)) -- We know this is not null because of the first GIGO check above
     99         END
    100         ELSE
    101         BEGIN
    102             -- Find most of the matches
    103             WHILE @ix > 0
    104             BEGIN
    105                 SELECT 
    106                     -- Get the current value
    107                       @item = LTRIM(RTRIM(SUBSTRING(@Text,@pix,(@ix - @pix)))) 
    108                     -- Move previous pointer to end of last found delimiter
    109                     , @pix = @ix + @del_len 
    110                     -- And update the values for next pass though the loop, finding the next match
    111                     , @ix = CHARINDEX(@Delimiter, @Text, (@ix + @del_len)) -- TODO: If you want to implment Case Insensitivity here, wrap both in LOWER()
    112                 
    113                 IF @item <> '' AND @item IS NOT NULL -- Only save non empty values
    114                     INSERT INTO @retTable(item) VALUES (@item)
    115             END
    116 
    117             -- Get the trailing text
    118             SET @item = LTRIM(RTRIM(SUBSTRING(@Text,@pix,@text_len)))
    119             IF @item <> '' AND @item IS NOT NULL  -- Only save non empty values
    120                 INSERT INTO @retTable(item) VALUES (@item)
    121         END --  @ix = 0
    122     END
    123     ELSE -- @del_len = 1
    124     BEGIN -- PATINDEX Mode (SLOW!) Try to pass in text that uses single char delimeters when possible
    125 
    126         DECLARE @del_pat nvarchar(3002)  -- Assume 3x @Delimiter + 2, for escaping every character plus wrapper %
    127 
    128         -- Escape characters that will mess up the like clause, and wrap in wild cards %
    129         SELECT @del_pat = '%' + REPLACE(REPLACE(REPLACE(@Delimiter
    130                 , '[','[[]')
    131                 , '%','[%]')
    132                 , '_', '[_]') 
    133             + '%'
    134 
    135         SELECT @ix = PATINDEX(@del_pat, @Text) -- TODO: If you want to implment Case Insensitivity here, wrap both in LOWER()
    136              , @pix = 0
    137     
    138         -- No delim found, just return the passed value, trimmed
    139         IF @ix = 0
    140         BEGIN
    141             INSERT INTO @retTable(item) 
    142                 SELECT LTRIM(RTRIM(@Text)) -- We know this is not null because of the first GIGO check above
    143         END
    144         ELSE
    145         BEGIN
    146             -- Find most of the matches
    147             WHILE @ix > 0
    148             BEGIN
    149                 SELECT 
    150                     -- Get the curent Item
    151                     @item = LTRIM(RTRIM(SUBSTRING(@Text,@pix,(@ix - @pix))))
    152                     -- Move the previous index to the end of the previous delimiter
    153                     , @pix = @ix + @del_len 
    154                     -- And set values for next itteration of the loop, finding the next match
    155                     , @ix = PATINDEX(@del_pat, SUBSTRING(@Text, (@ix + @del_len), @text_len)) -- TODO: If you want to implment Case Insensitivity here, wrap both in LOWER()
    156 
    157                 IF @item <> '' AND @item IS NOT NULL  -- Only save non empty values
    158                     INSERT INTO @retTable(item) VALUES (@item)
    159 
    160                 IF @ix > 0 SET @ix = ((@ix + @pix) - 1) -- -1 since PatIndex is 1 based and Substring is 0 based
    161             END
    162 
    163             -- Get the trailing text
    164             SET @item = LTRIM(RTRIM(SUBSTRING(@Text,@pix,@text_len)))
    165             IF @item <> '' AND @item IS NOT NULL  -- Only save non empty values
    166                 INSERT INTO @retTable(item) VALUES (@item)
    167         END --  @ix = 0
    168     END -- @del_len = 1
    169 
    170     RETURN
    171 END
    172 
    173 GO
    174 
    175 /*
    176 Overloaded version to make splitting comma seperated lists of ints easier.
    177 Note the delimiter is hard coded to comma and that non-int values will be removed.
    178 */
    179 CREATE FUNCTION [dbo].[FN_SPLIT_INT] (
    180     @Text nvarchar(max)  -- Text to split
    181 )
    182 RETURNS TABLE
    183 AS
    184 RETURN SELECT [item_int] -- TODO: Optional add distinct?
    185     FROM [dbo].[FN_SPLIT](@Text, ',') -- Hard coded to comma delimited
    186     WHERE [item_int] IS NOT NULL -- Remove invalid values
    187 GO
    188     
    189 
    190 GRANT REFERENCES, SELECT ON [dbo].[FN_SPLIT] TO [public] AS [dbo]
    191 GRANT REFERENCES, SELECT ON [dbo].[FN_SPLIT_INT] TO [public] AS [dbo]
    192 GO
    193 
    194 -- Precompile, so performance numbers below are not skewed.
    195 SELECT * INTO #Garbage1 FROM [dbo].[FN_SPLIT]('1,2,3',',')
    196 SELECT * INTO #Garbage2 FROM [dbo].[FN_SPLIT_INT]('1,2,3')
    197 
    198 DROP TABLE #Garbage1
    199 DROP TABLE #Garbage2
    200 GO
    201 
    202 --------------------------------------------------------------------------------------------------------------------------------------------
    203 --------------------------------------------------------------------------------------------------------------------------------------------
    204 --------------------------------------------------------------------------------------------------------------------------------------------
    205 -- Unit test --
    206 -- Split has been problematic enough for me I thought these tests important.
    207 
    208 SET NOCOUNT ON
    209 
    210 DECLARE @TESTS TABLE (
    211     [cnt] int,
    212     [txt] nvarchar(max),
    213     [delim] nvarchar(1000),
    214     [sum_len] int
    215 )
    216 DECLARE @RESULTS TABLE (
    217     [id] int identity(1,1),
    218     [msg] varchar(255) null,
    219     [pass_fail] AS (
    220         CASE 
    221             WHEN (ISNULL([expected_count],0) = ISNULL([actual_count],0) AND ISNULL([expected_sum_len],0) = ISNULL([actual_sum_len],0)) THEN 'PASS' 
    222             ELSE 'FAIL' 
    223             END
    224     ),
    225     [runtime] int null,
    226     [expected_count] int null,
    227     [actual_count] int null,
    228     [expected_sum_len] int null,
    229     [actual_sum_len] int null,
    230     [delim] nvarchar(1000),
    231     [txt] nvarchar(max)
    232 )
    233 
    234 DECLARE @BigText nvarchar(max)
    235 DECLARE @BigTextItemCount int
    236 DECLARE @BigTextSumHash int
    237 
    238 -- Alternative large volume tests, set to 10 for quick, set to 100K for a real workout
    239 --SELECT @BigTextItemCount = 10, @BigTextSumHash = 11
    240 SELECT @BigTextItemCount = 10000, @BigTextSumHash = 38894
    241 --SELECT @BigTextItemCount = 100000, @BigTextSumHash = 488895
    242 
    243 -- Create the hash of big text. I know this code is somewhat ugly, but it creates the large text in 
    244 -- about 1 second, as opposed to an itterative concat that took 14 minutes... :-)
    245 ;with cte as (
    246     select 9 as [i]
    247     union all
    248     select [i] - 1 FROM cte where [i] > 0
    249 ),
    250 crs as (
    251     SELECT ROW_NUMBER() OVER(ORDER BY c1.[i]) as [rn]
    252     FROM cte c1          -- 10
    253     CROSS JOIN cte c2 -- 100
    254     CROSS JOIN cte c3 -- 1000
    255     CROSS JOIN cte c4 -- 10000
    256     CROSS JOIN cte c5 -- 100000
    257 )
    258 SELECT @BigText =
    259     (
    260         (
    261         SELECT '#' + CAST([rn] as nvarchar(32))
    262         FROM crs
    263         WHERE [rn] <= @BigTextItemCount
    264         FOR XML PATH('') , TYPE
    265         ).value('.', 'nvarchar(max)')
    266     )
    267 
    268 -- Most of the tests go here --
    269 INSERT INTO @TESTS (cnt, sum_len, txt, delim)
    270     -- Basic 1-char Delim Tests
    271               SELECT 0, 0, '', ','
    272     UNION ALL SELECT 0, 0, null, ','
    273     UNION ALL SELECT 0, 0, 'a', null
    274     UNION ALL SELECT 0, 0, 'a', ''
    275     UNION ALL SELECT 3, 3, '1,2,3', ','
    276     UNION ALL SELECT 3, 3, ',1,2,3', ','
    277     UNION ALL SELECT 3, 3, '1,2,3,', ','
    278     UNION ALL SELECT 3, 3, ',1,2,3,', ','
    279     UNION ALL SELECT 3, 3, ' , 1 , 2 , 3 , ', ','
    280     UNION ALL SELECT 3, 3, ',,, , 1 , 2 , 3 , ,,,', ','
    281     UNION ALL SELECT 3, 3, 'a, b, c', ','
    282     UNION ALL SELECT 3, 3, 'a,b,c', ','
    283     UNION ALL SELECT 2, 6, 'Cat=Pub', '='
    284     UNION ALL SELECT 1, 1, 'a', ','
    285     UNION ALL SELECT 1, 1, '  a  ', ','
    286     -- 1 char Int Tests
    287     UNION ALL SELECT 10, 18, 'a,1,2,-1,-2,b,1.0,-1.0, 3 , -4 ,', ','
    288     -- Basic multi-char delim tests
    289     UNION ALL SELECT 0, 0, '', '<tag>'
    290     UNION ALL SELECT 0, 0, null, '<tag>'
    291     UNION ALL SELECT 0, 0, 'a', null
    292     UNION ALL SELECT 0, 0, 'a', ''
    293     UNION ALL SELECT 3, 3, '1<TaG>2<tag>3', '<tag>' -- Case Insensitivity test 1
    294     UNION ALL SELECT 3, 3, '<tag>1<tag>2<tag>3', '<TaG>' -- Case Insensitivity test 2
    295     UNION ALL SELECT 3, 3, '1<tag>2<tag>3<tag>', '<tag>'
    296     UNION ALL SELECT 3, 3, '<tag>1<tag>2<tag>3<tag>', '<tag>'
    297     UNION ALL SELECT 3, 3, ' <tag> 1 <tag> 2 <tag> 3 <tag> ', '<tag>'
    298     UNION ALL SELECT 3, 3, '<tag><tag><tag> <tag> 1 <tag> 2 <tag> 3 <tag> <tag><tag><tag>', '<tag>'
    299     UNION ALL SELECT 3, 3, 'a<tag> b<tag> c', '<tag>'
    300     UNION ALL SELECT 3, 3, 'a<tag>b<tag>c', '<tag>'
    301     UNION ALL SELECT 2, 6, 'Cat<tag>Pub', '<tag>'
    302     UNION ALL SELECT 1, 1, 'a', '<tag>'
    303     UNION ALL SELECT 1, 1, '  a  ', '<tag>'
    304     -- multi char delim Int Tests
    305     UNION ALL SELECT 10, 18, 'a<tag>1<tag>2<tag>-1<tag>-2<tag>b<tag>1.0<tag>-1.0<tag> 3 <tag> -4 <tag>', '<tag>'
    306     -- Delims with escape char % in it
    307     UNION ALL SELECT 0, 0, '', '<t%a%g>'
    308     UNION ALL SELECT 0, 0, null, '<t%a%g>'
    309     UNION ALL SELECT 0, 0, 'a', null
    310     UNION ALL SELECT 0, 0, 'a', ''
    311     UNION ALL SELECT 3, 3, '1<t%a%g>2<t%a%g>3', '<t%a%g>'
    312     UNION ALL SELECT 3, 3, '<t%a%g>1<t%a%g>2<t%a%g>3', '<t%a%g>'
    313     UNION ALL SELECT 3, 3, '1<t%a%g>2<t%a%g>3<t%a%g>', '<t%a%g>'
    314     UNION ALL SELECT 3, 3, '<t%a%g>1<t%a%g>2<t%a%g>3<t%a%g>', '<t%a%g>'
    315     UNION ALL SELECT 3, 3, ' <t%a%g> 1 <t%a%g> 2 <t%a%g> 3 <t%a%g> ', '<t%a%g>'
    316     UNION ALL SELECT 3, 3, '<t%a%g><t%a%g><t%a%g> <t%a%g> 1 <t%a%g> 2 <t%a%g> 3 <t%a%g> <t%a%g><t%a%g><t%a%g>', '<t%a%g>'
    317     UNION ALL SELECT 3, 3, 'a<t%a%g> b<t%a%g> c', '<t%a%g>'
    318     UNION ALL SELECT 3, 3, 'a<t%a%g>b<t%a%g>c', '<t%a%g>'
    319     UNION ALL SELECT 2, 6, 'Cat<t%a%g>Pub', '<t%a%g>'
    320     UNION ALL SELECT 1, 1, 'a', '<t%a%g>'
    321     UNION ALL SELECT 1, 1, '  a  ', '<t%a%g>'
    322     UNION ALL SELECT 10, 18, 'a<t%a%g>1<t%a%g>2<t%a%g>-1<t%a%g>-2<t%a%g>b<t%a%g>1.0<t%a%g>-1.0<t%a%g> 3 <t%a%g> -4 <t%a%g>', '<t%a%g>'
    323     -- Delims with escape char _ in it
    324     UNION ALL SELECT 0, 0, '', '<t_ag>'
    325     UNION ALL SELECT 0, 0, null, '<t_ag>'
    326     UNION ALL SELECT 0, 0, 'a', null
    327     UNION ALL SELECT 0, 0, 'a', ''
    328     UNION ALL SELECT 3, 3, '1<t_ag>2<t_ag>3', '<t_ag>'
    329     UNION ALL SELECT 3, 3, '<t_ag>1<t_ag>2<t_ag>3', '<t_ag>'
    330     UNION ALL SELECT 3, 3, '1<t_ag>2<t_ag>3<t_ag>', '<t_ag>'
    331     UNION ALL SELECT 3, 3, '<t_ag>1<t_ag>2<t_ag>3<t_ag>', '<t_ag>'
    332     UNION ALL SELECT 3, 3, ' <t_ag> 1 <t_ag> 2 <t_ag> 3 <t_ag> ', '<t_ag>'
    333     UNION ALL SELECT 3, 3, '<t_ag><t_ag><t_ag> <t_ag> 1 <t_ag> 2 <t_ag> 3 <t_ag> <t_ag><t_ag><t_ag>', '<t_ag>'
    334     UNION ALL SELECT 3, 3, 'a<t_ag> b<t_ag> c', '<t_ag>'
    335     UNION ALL SELECT 3, 3, 'a<t_ag>b<t_ag>c', '<t_ag>'
    336     UNION ALL SELECT 2, 6, 'Cat<t_ag>Pub', '<t_ag>'
    337     UNION ALL SELECT 1, 1, 'a', '<t_ag>'
    338     UNION ALL SELECT 1, 1, '  a  ', '<t_ag>'
    339     UNION ALL SELECT 10, 18, 'a<t_ag>1<t_ag>2<t_ag>-1<t_ag>-2<t_ag>b<t_ag>1.0<t_ag>-1.0<t_ag> 3 <t_ag> -4 <t_ag>', '<t_ag>'
    340     -- Semi Evil tests
    341     UNION ALL SELECT 2, 2, 'a~`!@#$%^&*()_+|-={}:;"''<>,.?/    b', '~`!@#$%^&*()_+|-={}:;"''<>,.?/    ' -- no []
    342     UNION ALL SELECT 2, 2, 'a~`!@#$%^&*()_+|-={}[]:;"''<>,.?/    b', '~`!@#$%^&*()_+|-={}[]:;"''<>,.?/    ' -- with []
    343     UNION ALL SELECT 2, 2, 'a' + CHAR(10) + CHAR(13) + 'b', CHAR(10) + CHAR(13) -- White space chars
    344     -- Big Text Tests
    345     UNION ALL SELECT @BigTextItemCount,@BigTextSumHash,@BigText, '#'
    346     UNION ALL SELECT @BigTextItemCount,@BigTextSumHash,REPLACE(@BigText,'#', '<tag>'), '<tag>'
    347 
    348 -- Loop through each of the tests, logging results
    349 DECLARE @txt nvarchar(max) -- Input text
    350 DECLARE @delim nvarchar(1000) -- Input delimiter
    351 DECLARE @cnt int -- Expected count
    352 DECLARE @sum_len int -- Expected sum(len(item))
    353 DECLARE @t_cnt int -- Actual count
    354 DECLARE @t_sum_len int -- Actual sum(len(item))
    355 DECLARE @start datetime -- Test Start time (for performance tracking)
    356 
    357 DECLARE cur CURSOR FAST_FORWARD FOR
    358     SELECT [cnt],[txt],[delim],[sum_len] FROM @TESTS
    359 OPEN cur
    360 FETCH cur INTO @cnt, @txt, @delim,@sum_len
    361 WHILE @@FETCH_STATUS = 0
    362 BEGIN
    363 
    364     SELECT @start = GetDate();
    365 
    366     -- Execute test
    367     SELECT @t_cnt = count(*), @t_sum_len = SUM(LEN(item))
    368         FROM [dbo].[FN_SPLIT](@txt, @delim)
    369     
    370     -- Log results
    371     INSERT INTO @RESULTS ([msg],[runtime],[expected_count],[actual_count],[expected_sum_len],[actual_sum_len],[delim],[txt])
    372         SELECT 'TEST', DATEDIFF(ms, @start,  GetDate()), @cnt, @t_cnt, @sum_len, ISNULL(@t_sum_len,0), @delim, @txt
    373     
    374     FETCH cur INTO @cnt, @txt, @delim,@sum_len
    375 END
    376 CLOSE cur
    377 DEALLOCATE cur
    378 
    379 ----------------------------------------------------------------------------------------------------------------------------------
    380 -- Extra tests that required additional coding
    381 DECLARE @int_test nvarchar(max)
    382 SELECT @int_test = N'a,1,2,-1,-2,b,1.0,-1.0, 3 , -4 ,'
    383 
    384 -- Basic int test, ensure int's are properly returned
    385 INSERT INTO @RESULTS ([msg],[runtime],[expected_count],[actual_count],[expected_sum_len],[actual_sum_len],[delim],[txt])
    386 SELECT 'Tested Ints 1', null, 6, count(*), null, null, ',', @int_test
    387     FROM [dbo].[FN_SPLIT](@int_test, ',') 
    388     WHERE [item_int] is not null
    389 
    390 -- Ensure text value associated with int values maps 1:1
    391 INSERT INTO @RESULTS ([msg],[runtime],[expected_count],[actual_count],[expected_sum_len],[actual_sum_len],[delim],[txt])
    392 SELECT 'Tested Ints 2', null, 6, count(*), null, null, ',', @int_test
    393     FROM [dbo].[FN_SPLIT](@int_test, ',') 
    394     WHERE CAST([item_int] as nvarchar(max)) = [item]
    395     and item_int is not null
    396 
    397 
    398 -- Split int tests
    399 SELECT @int_test = '1,-2,3'
    400 SELECT @start = GetDate();
    401 INSERT INTO @RESULTS ([msg],[runtime],[expected_count],[actual_count],[expected_sum_len],[actual_sum_len],[delim],[txt])
    402         SELECT 'Split Int: ' + @int_test, DATEDIFF(ms, @start,  GetDate()), 3, count(*), 2, SUM(item_int), '#', @int_test
    403         FROM [dbo].[FN_SPLIT_INT](@int_test)
    404 
    405 SELECT @int_test = '1,a,-2,b,3,c'
    406 SELECT @start = GetDate();
    407 INSERT INTO @RESULTS ([msg],[runtime],[expected_count],[actual_count],[expected_sum_len],[actual_sum_len],[delim],[txt])
    408         SELECT 'Split Int: ' + @int_test, DATEDIFF(ms, @start,  GetDate()), 3, count(*), 2, SUM(item_int), '#', @int_test
    409         FROM [dbo].[FN_SPLIT_INT](@int_test)
    410 
    411 SELECT @int_test = '1, -2, 3' -- Spaces between commas
    412 SELECT @start = GetDate();
    413 INSERT INTO @RESULTS ([msg],[runtime],[expected_count],[actual_count],[expected_sum_len],[actual_sum_len],[delim],[txt])
    414         SELECT 'Split Int: ' + @int_test, DATEDIFF(ms, @start,  GetDate()), 3, count(*), 2, SUM(item_int), '#', @int_test
    415         FROM [dbo].[FN_SPLIT_INT](@int_test)
    416 
    417 SELECT @int_test = ' 1, -2, 3 ' -- Leading/trailing
    418 SELECT @start = GetDate();
    419 INSERT INTO @RESULTS ([msg],[runtime],[expected_count],[actual_count],[expected_sum_len],[actual_sum_len],[delim],[txt])
    420         SELECT 'Split Int: ' + @int_test, DATEDIFF(ms, @start,  GetDate()), 3, count(*), 2, SUM(item_int), '#', @int_test
    421         FROM [dbo].[FN_SPLIT_INT](@int_test)
    422 
    423 SELECT @int_test = '999999999999999,1,-2,-3,-99999999999999999' -- Basic boundry testing
    424 SELECT @start = GetDate();
    425 INSERT INTO @RESULTS ([msg],[runtime],[expected_count],[actual_count],[expected_sum_len],[actual_sum_len],[delim],[txt])
    426         SELECT 'Split Int: ' + @int_test, DATEDIFF(ms, @start,  GetDate()), 3, count(*), -4, SUM(item_int), '#', @int_test
    427         FROM [dbo].[FN_SPLIT_INT](@int_test)
    428 
    429 SELECT @int_test = ' 1.0, -2.0, 3 ' -- Should only return ints
    430 SELECT @start = GetDate();
    431 INSERT INTO @RESULTS ([msg],[runtime],[expected_count],[actual_count],[expected_sum_len],[actual_sum_len],[delim],[txt])
    432         SELECT 'Split Int: ' + @int_test, DATEDIFF(ms, @start,  GetDate()), 1, count(*), 3, SUM(item_int), '#', @int_test
    433         FROM [dbo].[FN_SPLIT_INT](@int_test)
    434 
    435 ----------------------------------------------------------------------------------------------------------------------------------
    436 -- Runtime / Performance testing
    437 
    438 IF OBJECT_ID('tempdb..#t1') IS NOT NULL    DROP TABLE #t1
    439 IF OBJECT_ID('tempdb..#t2') IS NOT NULL    DROP TABLE #t2
    440 IF OBJECT_ID('tempdb..#t3') IS NOT NULL    DROP TABLE #t3
    441 
    442 SELECT @start = GetDate();
    443 SELECT [item] INTO #t1 FROM [dbo].[FN_SPLIT](@BigText, '#') 
    444 INSERT INTO @RESULTS ([msg],[runtime],[expected_count],[actual_count],[expected_sum_len],[actual_sum_len],[delim],[txt])
    445         SELECT 'Speed 1: Item only', DATEDIFF(ms, @start,  GetDate()), null, null, null, null, '#', @BigText
    446 
    447 
    448 SELECT @start = GetDate();
    449 SELECT [item_int] INTO #t3 FROM [dbo].[FN_SPLIT](@BigText, '#') 
    450 INSERT INTO @RESULTS ([msg],[runtime],[expected_count],[actual_count],[expected_sum_len],[actual_sum_len],[delim],[txt])
    451         SELECT 'Speed 2: Item Int Only', DATEDIFF(ms, @start,  GetDate()), null, null, null, null, '#', @BigText
    452 
    453 SELECT @start = GetDate();
    454 SELECT [item] INTO #t2 FROM [dbo].[FN_SPLIT](@BigText, '#') WHERE [item_int] IS NOT NULL
    455 INSERT INTO @RESULTS ([msg],[runtime],[expected_count],[actual_count],[expected_sum_len],[actual_sum_len],[delim],[txt])
    456         SELECT 'Speed 3: Item With Int Filter', DATEDIFF(ms, @start,  GetDate()), null, null, null, null, '#', @BigText
    457 
    458 IF OBJECT_ID('tempdb..#t1') IS NOT NULL    DROP TABLE #t1
    459 IF OBJECT_ID('tempdb..#t2') IS NOT NULL    DROP TABLE #t2
    460 IF OBJECT_ID('tempdb..#t3') IS NOT NULL    DROP TABLE #t3
    461 
    462 ----------------------------------------------------------------------------------------------------------------------------------
    463 /*
    464 -- Ensure test failures work
    465 INSERT INTO @RESULTS ([msg],[runtime],[expected_count],[actual_count],[expected_sum_len],[actual_sum_len],[delim],[txt])
    466         SELECT 'INTENTIONAL FAILURE', null, 1, 2, 3, 4, '', ''
    467 */
    468 
    469 -- Display results
    470 SELECT * 
    471 FROM @RESULTS
    472 ORDER BY CASE [pass_fail] WHEN 'FAIL' THEN 0 ELSE 1 END ASC, [id] ASC
    473 
    474 -- And Total runtime
    475 SELECT SUM(ISNULL(runtime,0)) as [total_runtime] FROM @RESULTS
    476 
    477 -- Raise errors as needed.
    478 IF (SELECT count(*) FROM @RESULTS WHERE [pass_fail] = 'FAIL') > 0
    479     RAISERROR('Unexpected results.  Review results table for details.',18,1)
    480 GO
      1 SET ANSI_NULLS ON;
      2 SET ANSI_PADDING ON;
      3 SET ANSI_WARNINGS ON;
      4 SET CONCAT_NULL_YIELDS_NULL ON;
      5 SET NUMERIC_ROUNDABORT OFF;
      6 SET QUOTED_IDENTIFIER ON;
      7 GO
      8 
      9 IF OBJECT_ID('[dbo].[PR_FIND]') IS NOT NULL
     10     DROP PROCEDURE [dbo].[PR_FIND]
     11 GO
     12 
     13 CREATE PROCEDURE [dbo].[PR_FIND]
     14 /* ------------------------------------------------------------------------------------------------
     15 Purpose: Examples (finding self, since we know it exists)
     16 
     17 EXEC [PR_FIND] '@DisplayLevel'
     18 EXEC [PR_FIND] 'PR_FIND', 0
     19 EXEC [PR_FIND] 'PR_FIND', 1, 50
     20 
     21 
     22 Revision history:           
     23 ---------------------------------------------------------------------------------------------------                           
     24 Date            User                                Change Description                     
     25 ---------------------------------------------------------------------------------------------------
     26 10/11/2013      Brad Joss (bradjoss@hotmail.com)    Initial Public Draft
     27 01/20/2014        Brad Joss                            Fixed line number bug where line numbers were incorrect.
     28 01/23/2014        Brad Joss                            When fixing the above bug, I saw some items that might
     29                                                     be nice to have, so I added a few features:
     30                                                     - Will now match on name, not just body text.  Name can be fully qualified
     31                                                         since functionally it will do an OBJECT_ID() match.
     32                                                     - Display more details in run summary including total matches
     33                                                       and a better breakdown of where time was spent while searching.
     34                                                     - Display match_count and name_match in summary results.
     35 ---------------------------------------------------------------------------------------------------*/
     36 (
     37     @TextToFind nvarchar(max),  -- Pattern to search on
     38     @DisplayLevel tinyint = 1, -- 0 = Object Summary Only, 1 = Windowed Results, 2 = Full text
     39     @Lead int = 40, -- Amount of text to show the left and right of the result in Windowed mode
     40     @SearchJobsToo bit = 1,  -- When true, search SQL Agent Job Steps
     41     @IncludeObjects nvarchar(max) = null, -- Comma separated list of objects scope search to
     42     @ExcludeObjects nvarchar(max) = null -- Comma separated list of objects exempt from search
     43 )
     44 AS
     45 BEGIN
     46     SET NOCOUNT ON
     47 
     48     DECLARE   @id int -- Object ID
     49             , @name nvarchar(max) -- Full Object Name
     50             , @Text nvarchar(max)    -- Object Text
     51             , @DisplayText nvarchar(max)    -- Text to Output
     52             , @index int -- index of start of display window (@match_index - @Lead)
     53             , @match_index int -- Actual index where we found the match
     54             , @matchCount int -- Matches found for the text
     55             , @spanLen int -- Length of display window
     56             , @lenText int -- Curent LEN(@Text)
     57             , @lenTextToFind int -- LEN(@TextToFind)
     58             , @StartTime DateTime -- Runtime Start
     59             , @OriginalTextToFind nvarchar(max) -- Backup of what is being searched on before we adjust it
     60             , @create_date datetime -- Object create date
     61             , @modify_date datetime -- Object mod date
     62             , @type_desc nvarchar(60) -- Object type 
     63             , @total_lines int -- Runing total of found new lines
     64             , @current_line_number int -- Line number current match is found on (starting from CREATE statement)
     65             , @total_start_time datetime -- Start time of the SP call, for kicks
     66             , @skip_lines int -- When we skip a body of text, keep track of line count for skipped text
     67             , @new_line nvarchar(max) -- Character(s) to seek when counting new lines
     68             , @scan_time int -- Time it took to scan for matches
     69             , @passed_object_id int -- If they passed in a full object name, let's know about it here...
     70     
     71     
     72     -- Just to make sure we are keeping the code well optimzed, display runtimes
     73     SELECT @total_start_time = GetDate()
     74     
     75     -- Get this before we do any transformations on the text
     76     SELECT @passed_object_id = OBJECT_ID(@TextToFind)
     77 
     78     -- Backup what is being searched on so when we display it it doesn't look funny
     79     SELECT @OriginalTextToFind = @TextToFind
     80     
     81     -- Wrap the text in wild card wrappers and remove %%, setting to LOWER() so all matches will be case insenstive later on
     82     SELECT @TextToFind = REPLACE('%' + LOWER(@TextToFind) + '%', '%%','%')
     83 
     84 
     85     -- If they passed something that maps 1:1 to an object, escape out the bracets so we match on object name,
     86     -- not character array patterns when searching on text.  The actual name match will be based on object_id
     87     IF @passed_object_id IS NOT NULL
     88         SELECT @TextToFind = REPLACE(@TextToFind,'[','[[]')
     89 
     90     -- Even if we actually use 10/13, the way we are counting should still work. 
     91     -- If you move to more than 1 newline character, then the character count will have
     92     -- to be factored into the delta counts (e.g. (LEN() - LEN(REPLACE()))/LEN(@new_line))
     93     SELECT @new_line = CHAR(10) 
     94     
     95     -- Parmeter checks
     96     IF ISNULL(@DisplayLevel,-1) NOT IN (0,1,2)
     97         SET @DisplayLevel = 1 -- Default to windowed mode
     98     IF @Lead IS NULL OR @Lead < 0
     99         SET @Lead = 40 -- Default lead lenght
    100     IF @SearchJobsToo IS NULL
    101         SET @SearchJobsToo = 1
    102     IF RTRIM(@IncludeObjects) = ''
    103         SET @IncludeObjects = NULL
    104     IF RTRIM(@ExcludeObjects) = ''
    105         SET @ExcludeObjects = NULL
    106         
    107     
    108     -- Table to store the matched objects 
    109     DECLARE @oids TABLE (
    110         [id] int, -- [object_id]
    111         [name] nvarchar(512),
    112         [schema_name] nvarchar(512),
    113         [full_name] as (
    114             ISNULL('[' + [schema_name] + '].', '') + '[' + [name] + ']'
    115         ),
    116         [create_date] datetime,
    117         [modify_date] datetime,
    118         [type_desc] nvarchar(60),
    119         [txt] nvarchar(max),
    120         [name_match] bit default(0),
    121         [match_count] int null
    122     )
    123 
    124     -- Table to store what objects to return
    125     DECLARE @scope_objects TABLE (
    126         [object_id] int,
    127         [include] bit not null default(0)
    128     )
    129 
    130     -- If they have indicated they want to filter by object name, create a list of
    131     -- valid objects to be searched.
    132     IF @IncludeObjects IS NOT NULL OR @ExcludeObjects IS NOT NULL
    133     BEGIN
    134         -- Explicitly omit these items
    135         ;with cte as (
    136             SELECT o.id
    137             FROM sysobjects o
    138             INNER JOIN [dbo].[FN_SPLIT](@ExcludeObjects,',') f
    139                 on (UPPER(o.name) LIKE LTRIM(RTRIM(UPPER([item])))) -- Case insensitive non-wildcard match, so we can actually match on a specific object name if need be
    140                 AND RTRIM([item]) <> ''
    141         )
    142         INSERT INTO @scope_objects ([object_id],[include])
    143         SELECT DISTINCT [id], 0
    144         FROM cte
    145         WHERE [id] IS NOT NULL
    146 
    147         IF @@ROWCOUNT = 0
    148             SELECT @ExcludeObjects = NULL -- Later, we check to see if we should filter based on @ExcludeObjects and @IncludeObjects nullness
    149         
    150         -- Scope search to only include these items
    151         ;with cte as (
    152             SELECT o.id
    153             FROM sysobjects o
    154             INNER JOIN [dbo].[FN_SPLIT](@IncludeObjects,',') f
    155                 on (UPPER(o.name) LIKE LTRIM(RTRIM(UPPER([item])))) -- Case insensitive non-wildcard match, so we can actually match on a specific object name if need be
    156                 AND RTRIM([item]) <> ''
    157         )
    158         INSERT INTO @scope_objects ([object_id],[include])
    159         SELECT DISTINCT [id], 1
    160         FROM cte
    161         WHERE [id] IS NOT NULL
    162 
    163         IF @@ROWCOUNT = 0
    164             SELECT @IncludeObjects = NULL -- Later, we check to see if we should filter based on @ExcludeObjects and @IncludeObjects nullness
    165         ELSE
    166         BEGIN
    167             -- If they have indicated that the want to include and exclude at the same time
    168             -- check for object overlap, and default to exclude the item so we can allow a 
    169             -- pattern like:
    170             -- Show me all where "aaa%", except "aaa_111"
    171             DELETE FROM @scope_objects
    172             WHERE [include] = 1
    173             AND [object_id] IN (
    174                 SELECT [object_id]
    175                 FROM @scope_objects
    176                 WHERE [include] = 0
    177             )
    178             
    179             -- If items were deleted, recheck to see if there are any includes left
    180             IF @@ROWCOUNT > 0
    181                 IF NOT EXISTS (SELECT * FROM @scope_objects WHERE [include] = 1)
    182                     SELECT @IncludeObjects = NULL, @ExcludeObjects = NULL -- Later, we check to see if we should filter based on @ExcludeObjects and @IncludeObjects nullness
    183         END
    184     END -- @IncludeObjects IS NOT NULL OR @ExcludeObjects IS NOT NULL
    185 
    186     -- Find matches 
    187     INSERT INTO @oids (id, name, [schema_name], [type_desc], txt, [create_date], [modify_date], [name_match])
    188         select distinct c.id, c.name, [schema_name], [type_desc], c.txt, [create_date], [modify_date], 
    189                 CASE 
    190                     -- Substring of a name is what was passed
    191                     WHEN LOWER(name) LIKE @TextToFind THEN 1 
    192                     WHEN 
    193                     (
    194                         -- Fully qualified name was passed
    195                         @passed_object_id IS NOT NULL 
    196                         AND 
    197                         c.id = @passed_object_id
    198                     ) THEN 1 
    199                 ELSE 0 END
    200             from [dbo].[VW_OBJ_TXT] c
    201             -- That case insensitive match our search text
    202             where 
    203             (
    204                 LOWER(c.[txt]) like @TextToFind -- Body match
    205                 OR
    206                 LOWER(name) LIKE @TextToFind -- Name match
    207                 OR
    208                 (
    209                     -- In case they pass in "[dbo].[MyObject]" versus "dbo.MyObject" versus "MyObject"
    210                     -- Try to give them clues as to what they may be missing out on
    211                     @passed_object_id IS NOT NULL 
    212                     AND 
    213                     c.id = @passed_object_id
    214                 )
    215             )
    216             -- And are either in our include list, or no list was passed
    217             and 
    218             (
    219                 @IncludeObjects is null
    220                 OR
    221                 c.id IN (select [object_id] from @scope_objects where [include] = 1)
    222             )
    223             -- And are not in our exclude list, or no list was passed
    224             and
    225             (
    226                 @ExcludeObjects is null
    227                 OR
    228                 c.id NOT IN (select [object_id] from @scope_objects where [include] = 0)
    229             )
    230 
    231     -- If they have indicated to search job text, do so here.
    232     IF @SearchJobsToo = 1
    233     BEGIN
    234         INSERT INTO @oids (id, name, [schema_name], [type_desc], txt, [create_date], [modify_date], [name_match])
    235         SELECT    DISTINCT
    236             (-1 * ROW_NUMBER()OVER(ORDER BY j.name, js.step_name)), -- Since job_id's are not int, job_step_id might be confusing, get arbitrary negative number
    237             ISNULL('JOB: ' + j.name, 'Unknown Job') + ISNULL(', STEP: ' + js.step_name, 'Unknown Step'),
    238             'job',
    239             'SQL Agent Job Step',
    240             js.command,  -- Job Step Text
    241             j.date_created,
    242             j.date_modified,
    243             CASE 
    244                 WHEN LOWER(j.name) LIKE @TextToFind THEN 1 
    245                 WHEN LOWER(js.step_name) LIKE @TextToFind THEN 1 
    246                 ELSE 0 
    247             END
    248         FROM    msdb.dbo.sysjobs j
    249         JOIN    msdb.dbo.sysjobsteps js
    250             ON    js.job_id = j.job_id 
    251         WHERE 
    252         (
    253             LOWER(js.command) like @TextToFind -- Case insensitive
    254             OR
    255             LOWER(j.name) LIKE @TextToFind
    256             OR
    257             LOWER(js.step_name) LIKE @TextToFind
    258         )
    259     END
    260 
    261     SELECT @scan_time = DATEDIFF(ms, @total_start_time, GetDate())
    262 
    263     IF @DisplayLevel > 0
    264     BEGIN
    265         -- Horizontal rules to break up the results
    266         DECLARE @C_OBJECT_HR nvarchar(max) = '/******************************************************************************/'
    267         DECLARE @C_MATCH_HR nvarchar(max)  = '--------------------------------------------------------------------------------'
    268         
    269         -- Cache this value once before we enter into loop
    270         SELECT @lenTextToFind = LEN(@TextToFind) - 2 -- -2 = Trimming the %
    271         IF @lenTextToFind < 0
    272             SELECT @lenTextToFind = 0
    273 
    274         PRINT @C_OBJECT_HR
    275     
    276         -- Loop though the results, getting the multiple matches within the body of the text
    277         DECLARE DispCur CURSOR FAST_FORWARD FOR
    278         SELECT
    279             o.id,
    280             [full_name],
    281             o.txt,
    282             create_date,
    283             modify_date,
    284             type_desc
    285         from @oids o
    286         ORDER BY LOWER([full_name]) ASC
    287         
    288         OPEN DispCur
    289         FETCH DispCur INTO @id, @name, @Text, @create_date, @modify_date, @type_desc
    290         
    291         WHILE @@FETCH_STATUS = 0
    292         BEGIN
    293             -- Object match template, add details here to display information about the match per object
    294             PRINT 'ID: ' + CAST(@id as varchar(64))
    295             PRINT 'NAME: ' + @name
    296             PRINT 'TYPE: ' + @type_desc
    297             PRINT 'CREATED: ' + CAST(@create_date as nvarchar(max)) + ', MODIFIED: ' + CAST(@modify_date as nvarchar(max))
    298             PRINT 'SEARCH: "' + @OriginalTextToFind + '"'
    299             PRINT @C_MATCH_HR
    300             
    301             IF @DisplayLevel = 1 -- Windowed display mode
    302             BEGIN
    303                 SELECT @StartTime = GetDate() -- For the search time of this one object (not the whole routine), for kicks
    304                 SELECT @index = PATINDEX(@TextToFind, LOWER(@Text)) -- Search for our matching pattern
    305                 SELECT @match_index = @index
    306                 SELECT @matchCount = 0
    307                 SELECT @DisplayText = ''
    308                 SELECT @total_lines = 0
    309 
    310                 -- Find all occurences of the pattern --
    311                 WHILE @index > 0
    312                 BEGIN
    313                     -- Get the count of new line characters, then adding on matches from previous blocks of text
    314                     SELECT @current_line_number = (LEN(SUBSTRING(@Text, 1, @index)) - LEN(REPLACE(SUBSTRING(@Text, 1, @index), @new_line, '')))
    315 
    316                     -- Buffer the common search values in variables
    317                     SELECT @matchCount = @matchCount + 1
    318                     SELECT @lenText = LEN(@Text)
    319                     -- Set the start @index in bounds
    320                     SELECT @index = CASE 
    321                             WHEN @index > @Lead THEN @index - @Lead
    322                             ELSE 0
    323                         END
    324                     -- Size of the display window
    325                     SELECT @spanLen = LEN(@TextToFind) + (2*@Lead)
    326                     
    327                     -- If the search window is longer than the search text, narrow it
    328                     IF @spanLen + @index > @lenText
    329                         SELECT @spanLen = @lenText - @index
    330 
    331                     -- Display code snippet --
    332                     SELECT @DisplayText = @DisplayText + '
    333 Match ' + CAST(@matchCount as varchar(32)) + ' on line ' + CAST((@current_line_number + @total_lines) as varchar(32)) + ' within ' + @name + '
    334 ' + @C_MATCH_HR + '
    335 ...' + SUBSTRING(@Text, @index, @spanLen) + '...
    336 '
    337 
    338                     -- If the search window covered to the end of the text, end searching
    339                     IF (@match_index + @Lead) >= @lenText
    340                         SELECT @index = 0
    341                     ELSE
    342                     BEGIN
    343                         -- Keep track of how many lines will be skipped by advancing the seek start by the window length
    344                         SELECT @skip_lines = LEN(SUBSTRING(@Text, @match_index, @Lead)) - LEN(REPLACE(SUBSTRING(@Text, @match_index, @Lead), @new_line, ''))
    345                         
    346                         -- Else rescope the text to be searched to what remains and re-search
    347                         SELECT @Text = SUBSTRING
    348                         (
    349                             @Text, 
    350                             @match_index + @Lead, 
    351                             @lenText - (@match_index + @Lead)
    352                         )
    353                         SELECT @index = PATINDEX(@TextToFind, LOWER(@Text)) -- Find the next match
    354                         SELECT @match_index = @index
    355                         SELECT @total_lines = @total_lines + @current_line_number + @skip_lines -- Keep running total of line numbers
    356                     END
    357                 END -- While (finding all matches in object)
    358 
    359                 IF @matchCount = 0
    360                     SELECT @DisplayText = @DisplayText + 'No body matches found, name match only.'
    361 
    362                 -- Footer template, displayed at the end of each object that matches
    363                 SELECT @DisplayText = @DisplayText + '
    364 ' + @C_MATCH_HR + '
    365 "' + @OriginalTextToFind + '" Found ' 
    366 + CAST(@matchCount as varchar(32)) + ' Time' + (CASE WHEN @matchCount = 1 THEN '' ELSE 's' END) + ' within ' + @name + ' in '
    367 + CAST(DATEDIFF(ms, @StartTime, GetDate()) as varchar(32)) + 'ms
    368 '                
    369 
    370                 EXEC PR_PRINT @DisplayText
    371 
    372                 UPDATE @oids SET [match_count] = @matchCount WHERE [id] = @id
    373 
    374             END -- @DisplayLevel = 1
    375             ELSE -- Mode 2
    376             BEGIN
    377                 -- ELSE Display full code --    
    378                 EXEC PR_PRINT @Text
    379             END
    380             
    381             PRINT @C_OBJECT_HR
    382         
    383             FETCH DispCur INTO @id, @name, @Text, @create_date, @modify_date, @type_desc
    384         END
    385         CLOSE DispCur
    386         DEALLOCATE DispCur
    387     END -- @DisplayLevel > 0
    388 
    389 
    390     -- Display summary at the bottom so we have match counts --
    391     -- I would prefer this to be at the top, but I like the match count...
    392     SELECT 
    393         [id], 
    394         CONVERT(varchar(19), [modify_date], 120) as [modify_date],
    395         [type_desc],
    396         [full_name],
    397         [name_match],
    398         [match_count]
    399     from  @oids t
    400     ORDER BY LOWER(t.name) ASC
    401 
    402     DECLARE @Message nvarchar(max)
    403     DECLARE @TotalRuntime int
    404     DECLARE @TotalMatches int
    405 
    406     SELECT @TotalMatches = 
    407         (SELECT SUM(match_count) FROM @oids)
    408         +
    409         (SELECT COUNT(*) FROM @oids where [name_match] = 1);
    410 
    411     SELECT @TotalRuntime = DATEDIFF(ms, @total_start_time, GetDate())
    412     SELECT @Message = 'Search completed. '
    413         + 'Found ' + CAST(@TotalMatches as nvarchar(max)) +' match' + CASE WHEN @TotalMatches = 1 THEN '' ELSE 'es' END + '. '
    414         + CAST(@scan_time as nvarchar(max)) +'ms Scan Time. '
    415         + CAST((@TotalRuntime - @scan_time) as nvarchar(max)) + 'ms Format Time. '
    416         + 'Total Runtime: ' + CAST((@TotalRuntime + 500)/1000 as nvarchar(max)) + ' seconds.' -- + 500 so we round up at 1/2 second
    417     PRINT ''
    418     PRINT @Message
    419 END
    420 GO
    421 
    422 GRANT EXECUTE ON [dbo].[PR_FIND] TO [public] AS [dbo]
    423 GO
      1 IF OBJECT_ID('dbo.PR_PRINT') IS NOT NULL
      2     DROP PROCEDURE dbo.PR_PRINT
      3 GO
      4 
      5 CREATE PROCEDURE [dbo].[PR_PRINT]
      6 (
      7     @txt NVARCHAR(MAX) -- Text to print out
      8 )
      9 AS
     10 
     11 /*
     12 This was originally posted on SQLServerCentral.com at 
     13 http://www.sqlservercentral.com/scripts/Print/63240/
     14 
     15 Modifed by Brad Joss 10/11/13 to break on space as well
     16 
     17 This procedure is designed to overcome the limitation
     18 in the SQL print command that causes it to truncate strings
     19 longer than 8000 characters (4000 for nvarchar) in SQL Server
     20 management studio.
     21 
     22 It will print the text passed to it in substrings smaller than 4000
     23 characters.  If there are carriage returns (CRs) or new lines (NLs in the text),
     24 it will break up the substrings at the carriage returns and the
     25 printed version will exactly reflect the string passed.
     26 
     27 If there are insufficient line breaks in the text, it will
     28 print it out in blocks of 4000 characters with an extra carriage
     29 return at that point.
     30 
     31 If it is passed a null value, it will do virtually nothing.
     32 
     33 NOTE: This is substantially slower than a simple print, so should only be used
     34 when actually needed.
     35 */
     36 
     37 SET NOCOUNT ON
     38 
     39 DECLARE
     40     @cur_end BIGINT, -- track the length of the next substring 
     41     @offset tinyint, -- tracks the amount of offSET needed 
     42     @pg_sz int, -- size of printing window
     43     @char_idx bigint, -- index of the next newline character in the window
     44     @temp_char_idx bigint, -- to avoid having to substring/reverse the window twice, save the reverse index of a found space here
     45     @start_idx int, -- try to break on a known character combination
     46     @txt_len bigint -- current lenght of the text, basically a cache since we potentially will need it twice in the loop
     47 
     48 SET @pg_sz = 4000 -- Set window size to 4000 characters
     49 
     50 -- If size is in bounds (ie, small text), just print it and exit
     51 IF LEN(@txt) <= @pg_sz
     52 BEGIN
     53     PRINT @txt
     54     RETURN
     55 END
     56 
     57 SET @txt = replace(  replace(@txt, char(13) + char(10), char(10))   , char(13), char(10)) -- Standardize what a new line looks like to char(10)
     58 SELECT @txt_len = LEN(@txt)
     59 
     60 WHILE @txt_len > 1
     61 BEGIN
     62     
     63     -- Try to break on a new line
     64     SET @char_idx = CHARINDEX(CHAR(10), @txt)
     65 
     66     -- If we can't find a new line, try to break on a space where the space is near
     67     -- the end of the current page of text
     68     IF NOT (@char_idx between 1 AND @pg_sz)
     69     BEGIN
     70         -- Get the size of the page of text
     71         SELECT @char_idx = CASE WHEN (@txt_len < @pg_sz) THEN @txt_len ELSE @pg_sz END
     72         
     73         -- Look for the last space character in the page of text
     74         SET @temp_char_idx = CHARINDEX(' ', REVERSE(SUBSTRING(@txt, 1, @char_idx)))
     75 
     76         -- If found, set the position of the found character on the non-reversed string
     77         IF @temp_char_idx > 0
     78             SET @char_idx = (@char_idx - @temp_char_idx) + 1 -- +1 here since we -1 later on
     79         ELSE -- Indicate character is still not found
     80             SET @char_idx = 0
     81     END
     82 
     83     -- Try to break on a known char (newline or space) --
     84     IF @char_idx between 1 AND @pg_sz
     85     BEGIN
     86         -- Since we know the character we are breaking on is white space (new line or space)
     87         -- don't print it (hence the -1)
     88         SET @cur_end =  @char_idx - 1
     89         SET @offset = 2
     90     END
     91     ELSE
     92     BEGIN
     93         -- Else, just break at the window size.
     94         SET @cur_end = @pg_sz
     95         SET @offset = 1
     96     END
     97 
     98     -- Print the section
     99     PRINT SUBSTRING(@txt, 1, @cur_end)
    100 
    101     -- Remote the printed text from what remains to be printed.
    102     SET @txt = SUBSTRING(@txt, @cur_end+@offset, 1073741822)
    103     SELECT @txt_len = LEN(@txt)
    104 
    105 END /*End While loop*/
    106 
    107 -- Print any leftovers
    108 PRINT @txt
    109 
    110 GO
    111 
    112 GRANT EXECUTE ON [dbo].[PR_PRINT] TO [public]
    113 GO
    114 
    115 PRINT 'Basic Test:'
    116 EXEC dbo.PR_PRINT 'aaa bbb ccc d' 
    117 
    118 
    119 PRINT ''
    120 PRINT 'More Complicated Test:'
    121 
    122 DECLARE @BigText nvarchar(max)
    123 DECLARE @WindowSize int
    124 SET @WindowSize = 4000
    125 SELECT @BigText = CAST(REPLICATE('a',@WindowSize-1) as nvarchar(max))
    126      + CAST(' ' as nvarchar(max))
    127      + CAST(REPLICATE('b',@WindowSize-1) as nvarchar(max))
    128      + CAST(CHAR(10) as nvarchar(max))
    129      + CAST(REPLICATE('c',@WindowSize-1) as nvarchar(max))
    130      + CAST('xxx' as nvarchar(max))
    131      + CAST(REPLICATE('d',@WindowSize-1)as nvarchar(max))
    132 
    133 
    134 EXEC dbo.PR_PRINT @BigText
    135 GO
    IF OBJECT_ID('VW_OBJ_TXT') IS NOT NULL
        DROP VIEW [dbo].[VW_OBJ_TXT]
    GO
    
    CREATE VIEW [dbo].[VW_OBJ_TXT]
    /*******************************************************************************
    * Name          : dbo.VW_OBJ_TXT
    * Author        : bradjoss@hotmail.com
    * Purpose       : Helper view to make searching thorough code easier 
    ******************************************************************************
    * Change Date    Change By            Change DSC
    * -----------    -------------        --------------------------------------
    * 10/05/2012    Brad Joss            Initial Draft
    * 01/24/2014    Brad Joss            Return records with null text bodies, like tables.
                                        Nixed cast on get from syscomments, it was unnecessary.
    *******************************************************************************/
    AS
    with cte as 
    (
        select 
            o.[object_id] as [id],
            o.[schema_id],
            SCHEMA_NAME(o.[schema_id]) as [schema_name],
            o.name,
            o.type,
            o.type_desc,
            o.create_date,
            o.modify_date,
            (
                (
                    -- Concatenate together all the sections of code that make up a given object
                    SELECT c2.[text]
                    FROM syscomments c2 (nolock)
                    WHERE c2.[id] = o.[object_id]
                    ORDER BY colid
                    FOR XML PATH('') , TYPE
                ).value('.', 'nvarchar(max)')
            ) AS [txt]
        from sys.objects o
    )
    SELECT
        cte.*
        /*
        -- These extend the potency of the view for doing code comparison.  Since this view is only
        -- being posted as part of another suite of code, I have commmented them out.  Uncomment if
        -- they would be useful to you in some way.
        , LEN([txt]) AS [txt_len]
    
        -- The name swap is to contend with cases where the object text may be the same but the name
        -- differs, as might be the case with an automatic constraint name.
        , CHECKSUM(REPLACE(txt, name, '##NAME##')) as [checksum]
        */
    FROM cte
    -- WHERE [txt] IS NOT NULL -- Allow returing of objects w/o bodies
    GO
    
    -- Ensure it compiles
    select top 3 * from [VW_OBJ_TXT]
    GO

    https://www.codeproject.com/Tips/667730/T-SQL-Search-SQL-Code  

    EXEC [dbo].[PR_FIND] 'Sys_Role'
  • 相关阅读:
    取2个日期间的天数
    C#代码与JAVASCRIPT函数的相互调用
    ASP.NET验证码(3种)
    VS2008自带SQL 2005如何使用
    文本框默认有一个值,然后鼠标点上去那个值就清空
    远程桌面连接会话超时或者被限制改组策略也没用的时候就这么解决
    关于CComboBox的使用,编辑项的文字
    vc 剪切板 unicode
    Linux 防火墙、SELinux 的开启和关闭
    MSSQLSERVER服务不能启动
  • 原文地址:https://www.cnblogs.com/weifeng123/p/11114305.html
Copyright © 2020-2023  润新知