Login or Sign Up to become a member!
LessThanDot Sit Logo

LessThanDot

Data Management

Less Than Dot is a community of passionate IT professionals and enthusiasts dedicated to sharing technical knowledge, experience, and assistance. Inside you will find reference materials, interesting technical discussions, and expert tips and commentary. Once you register for an account you will have immediate access to the forums and all past articles and commentaries.

LTD Social Sitings

Lessthandot twitter Lessthandot Linkedin Lessthandot friendfeed Lessthandot facebook Lessthandot rss

Note: Watch for social icons on posts by your favorite authors to follow their postings on these and other social sites.

Your profile

    Search

    XML Feeds

    Google Ads

    « Parsing the Address field to its individual componentsSSIS runs in BIDS but not with SQL Agent »
    comments

    This blog was inspired by the following thread at MSDN Transact-SQL forum:
    Multiple values in field as parameter

    Given a string with values such as '201|203|301|400..600|725|800..900' return records from the table where Code field will be any of the values passed with | as a delimiter and also within the passed ranges where range indication will be ..

    The first idea that comes to mind is to use various splitting techniques available (see, for example, this excellent blog by Aaron Bertrand Splitting List of Integers or follow up by Brad Schulz Integer List Split - a SQL fable or the old times classic by Erland Sommarskog Arrays and Lists in SQL Server) split the list first by | and then, if needed by .. to introduce the ranges of values.

    In order to run the solution I implemented, we need to create a Numbers table in our database first (why it's important you can read in this classical article
    Why should I consider using an auxiliary Calendar table).

    Here is a script I use to create both Numbers and Calendar tables once in the Model database so all other user databases will inherit them:

    1. USE model
    2. GO
    3.  
    4. CREATE TABLE dbo.numbers (
    5.   number int NOT NULL
    6. )
    7.  
    8. ALTER TABLE dbo.numbers
    9. ADD
    10.   CONSTRAINT pk_numbers PRIMARY KEY CLUSTERED (number)
    11.    WITH FILLFACTOR = 100
    12. GO
    13.  
    14. INSERT INTO dbo.numbers (number)
    15. SELECT (a.number * 256) + b.number As number
    16. FROM     (
    17.     SELECT number
    18.     FROM  master..spt_values
    19.     WHERE type = 'P'
    20.     AND  number <= 255
    21.     ) As a
    22.  CROSS
    23.  JOIN (
    24.     SELECT number
    25.     FROM  master..spt_values
    26.     WHERE type = 'P'
    27.     AND  number <= 255
    28.     ) As b
    29. GO
    30.  
    31. CREATE TABLE dbo.calendar (
    32.   the_date   datetime NOT NULL
    33.  , is_monday  bit   NOT NULL
    34.  , is_tuesday  bit   NOT NULL
    35.  , is_wednesday bit   NOT NULL
    36.  , is_thursday bit   NOT NULL
    37.  , is_friday  bit   NOT NULL
    38.  , is_saturday bit   NOT NULL
    39.  , is_sunday  bit   NOT NULL
    40.  , is_weekend As (is_saturday ^ is_sunday)
    41.  , is_holiday  bit
    42.  , holiday_desc varchar(50)
    43. )
    44. GO
    45.  
    46. ALTER TABLE dbo.calendar
    47. ADD
    48.   CONSTRAINT pk_calendar PRIMARY KEY CLUSTERED (the_date)
    49.    WITH FILLFACTOR = 100
    50. GO
    51.  
    52. INSERT INTO dbo.calendar (the_date, is_monday, is_tuesday, is_wednesday, is_thursday, is_friday, is_saturday, is_sunday)
    53. SELECT the_date
    54.    , CASE WHEN DateDiff(dd, 0, the_date) % 7 = 0 THEN 1 ELSE 0 END As is_monday
    55.    , CASE WHEN DateDiff(dd, 0, the_date) % 7 = 1 THEN 1 ELSE 0 END As is_tuesday
    56.    , CASE WHEN DateDiff(dd, 0, the_date) % 7 = 2 THEN 1 ELSE 0 END As is_wednesday
    57.    , CASE WHEN DateDiff(dd, 0, the_date) % 7 = 3 THEN 1 ELSE 0 END As is_thursday
    58.    , CASE WHEN DateDiff(dd, 0, the_date) % 7 = 4 THEN 1 ELSE 0 END As is_friday
    59.    , CASE WHEN DateDiff(dd, 0, the_date) % 7 = 5 THEN 1 ELSE 0 END As is_saturday
    60.    , CASE WHEN DateDiff(dd, 0, the_date) % 7 = 6 THEN 1 ELSE 0 END As is_sunday
    61. FROM  (
    62.     SELECT DateAdd(dd, number, 0) As the_date
    63.     FROM  dbo.numbers
    64.     ) As dates

    Now, we need to create a function that will split the values. I chose the Numbers table variation of this function:

    1. CREATE FUNCTION [dbo].[fnSplit2](
    2.   @List       VARCHAR(MAX),
    3.    @Delimiter  VARCHAR(10)
    4. )
    5. RETURNS TABLE
    6. AS
    7.    RETURN
    8.    (
    9.        SELECT ROW_NUMBER() over (order by Number) as Pos,
    10.            Value = LTRIM(RTRIM(
    11.                SUBSTRING(@List, Number,
    12.                CHARINDEX(@Delimiter, @List + @Delimiter, Number) - Number)))
    13.        FROM
    14.            dbo.Numbers WITH (NOLOCK)
    15.        WHERE
    16.            Number <= CONVERT(INT, LEN(@List))
    17.            AND SUBSTRING(@Delimiter + @List, Number, LEN(@Delimiter)) = @Delimiter
    18.    );
    19. GO

    And now we can create our final solution:

    1. use AdventureWorks
    2. --select * from Sales.SalesOrderHeader order by CustomerID
    3.  
    4. declare @Param varchar(max)
    5. set @Param = '1|2|5|10..20|43|70..86|102'
    6.  
    7. declare @t table (iPos int, cValue varchar(max))
    8.  
    9.    insert into @t select * from dbo.fnSplit2(@Param, '|')
    10.  
    11.    if not exists (select 1 from @t where cValue like '%..%') -- we don't have to split by ..
    12.  
    13.       select distinct S.CustomerID from Sales.SalesOrderHeader S inner join @t T on S.CustomerID = T.cValue
    14.       ORDER BY S.CustomerID
    15.  
    16.    else
    17.  
    18.         begin
    19.  
    20.                 -- we need to split by .. now
    21.               if OBJECT_ID('TempDB..#TempRanges','U') IS NOT NULL
    22.                   drop table #TempRanges  
    23.                
    24.    --            select * from @t    
    25.                select iPos, cValue, max(case when Pos = 1 then Value end) as MinVal,
    26.  
    27.               max(case when Pos = 2 then Value end) as MaxVal into #TempRanges
    28.  
    29.             from (
    30.  
    31. select   T.*, F.Pos, F.Value from @t T OUTER apply dbo.fnSplit2(T.cValue, '..') F ) X
    32.  
    33.  GROUP BY iPos, cValue
    34.  
    35.  --select * from #TempRanges
    36.  
    37.             --select T1.CustomerID from Sales.SalesOrderHeader T1 where exists
    38.  
    39.             --(select 1 from #TempRanges X where cValue not like '%..%'   and T1.CustomerID = X.cValue)
    40.  
    41.             --union
    42.  
    43.             --select T.CustomerID from Sales.SalesOrderHeader T
    44.             --inner join #TempRanges X on
    45.             --T.CustomerID between X.MinVal and X.MaxVal and X.cValue LIKE '%..%'
    46.             --ORDER BY CustomerID
    47.            
    48.             select distinct T.CustomerID from Sales.SalesOrderHeader T
    49.             where  exists (select 1 from #TempRanges X where
    50.             T.CustomerID between X.MinVal and coalesce(X.MaxVal, X.MinVal))
    51.             ORDER BY CustomerID
    52.            
    53.      end

    There is a simpler XML based solution presented by Peso (Peter Larsson) in the following MSDN Thread:

    1. DECLARE @Sample TABLE
    2.     (
    3.         ID INT,
    4.         Data VARCHAR(100)
    5.     )
    6.  
    7. INSERT  @Sample
    8. VALUES  (1, '1000|5000')
    9.  
    10. DECLARE @ID INT = 1
    11.  
    12. -- Solution here
    13. ;WITH cteSource(ID, Data)
    14. AS (
    15.     SELECT  ID,
    16.         CAST('<v><i>' + REPLACE(REPLACE(Data, '|', '</i></v><v><i>'), '..', '</i><i>') + '</i></v>' AS XML) AS Data
    17.     FROM    @Sample
    18.     WHERE   ID = @ID
    19. )
    20. SELECT      s.ID,
    21.         v.value('i[1]', 'INT') AS [Start],
    22.         COALESCE(v.value('i[2]', 'INT'), v.value('i[1]', 'INT')) AS [End]
    23. FROM        cteSource AS s
    24. CROSS APPLY Data.nodes('v') AS n(v)




    *** Remember, if you have a SQL related question, try our Microsoft SQL Server Programming forum or our Microsoft SQL Server Admin forum

    About the Author

    Naomi Nosonovsky, Senior Software Developer, has more than 15 years of enterprise experience in analysis, design, development and implementation of high-performance client/server data management solutions. She is a Personality of the Year for 2008, 2009, 2010 and 2011 at www.universalthread.com in .NET, SQL Server & VFP categories. She is also an All Star contributor/MCC Community Contributor at forums.asp.net and MSDN T-SQL forum.
    Social SitingsFacebookLinkedIndeliciousLTD RSS Feed
    1855 views
    Instapaper

    No feedback yet

    Leave a comment


    Your email address will not be revealed on this site.

    Your URL will be displayed.
    (Line breaks become <br />)
    (Name, email & website)
    (Allow users to contact you through a message form (your email will not be revealed.)