TSQL/SQL Server - table function to parse/split delimited string to multiple/separate columns

久未见 提交于 2019-12-12 04:37:21

问题


So, my first post is less a question and more a statement! Sorry.

I needed to convert delimited strings stored in VarChar table columns to multiple/separate columns for the same record. (It's COTS software; so please don't bother telling me how the table is designed wrong.) After searching the internet ad nauseum for how to create a generic single line call to do that - and finding lots of how not to do that - I created my own. (The name is not real creative.)

Returns: A table with sequentially numbered/named columns starting with [Col1]. If an input value is not provided, then an empty string is returned. If less than 32 values are provided, all past the last value are returned as null. If more than 32 values are provided, they are ignored.

Prerequisites: A Number/Tally Table (luckily, our database already contained 'dbo.numbers').

Assumptions: Not more than 32 delimited values. (If you need more, change "WHERE tNumbers.Number BETWEEN 1 AND XXX", and add more prenamed columns ",[Col33]...,[ColXXX]".)

Issues: The very first column always gets populated, even if @InputString is NULL.

--======================================================================
--SMOZISEK 2017/09 CREATED
--======================================================================
CREATE FUNCTION dbo.fStringToPivotTable 
        (@InputString   VARCHAR(8000)
        ,@Delimiter     VARCHAR(30)         =   ','
        )
    RETURNS TABLE AS RETURN
    WITH    cteElements AS  (
        SELECT  ElementNumber       =   ROW_NUMBER() OVER(PARTITION BY @InputString ORDER BY (SELECT 0))
                ,ElementValue       =   NodeList.NodeElement.value('.','VARCHAR(1022)')
        FROM        (SELECT TRY_CONVERT(XML,CONCAT('<X>',REPLACE(@InputString,@Delimiter,'</X><X>'),'</X>')) AS InputXML)   AS InputTable
        CROSS APPLY InputTable.InputXML.nodes('/X')                                                                         AS NodeList(NodeElement)
    )
    SELECT  PivotTable.*
        FROM    (
            SELECT  ColumnName          =   CONCAT('Col',tNumbers.Number)
                    ,ColumnValue        =   tElements.ElementValue
            FROM        DBO.NUMBERS         AS  tNumbers                --DEPENDENT ON ANY EXISTING NUMBER/TALLY TABLE!!!
            LEFT JOIN   cteElements         AS  tElements
                ON      tNumbers.Number     =   tElements.ElementNumber
            WHERE       tNumbers.Number     BETWEEN 1 AND 32
        )   AS  XmlSource
    PIVOT (
        MAX(ColumnValue)
        FOR ColumnName
        IN  ([Col1] ,[Col2] ,[Col3] ,[Col4] ,[Col5] ,[Col6] ,[Col7] ,[Col8]
            ,[Col9] ,[Col10],[Col11],[Col12],[Col13],[Col14],[Col15],[Col16]
            ,[Col17],[Col18],[Col19],[Col20],[Col21],[Col22],[Col23],[Col24]
            ,[Col25],[Col26],[Col27],[Col28],[Col29],[Col30],[Col31],[Col32]
            )
    )   AS  PivotTable
    ;
    GO

Test:

SELECT  * 
FROM    dbo.fStringToPivotTable ('|Height|Weight||Length|Width||Color|Shade||Up|Down||Top|Bottom||Red|Blue|','|')   ;

Usage:

SELECT  1       AS ID,'Title^FirstName^MiddleName^LastName^Suffix' AS Name
INTO    #TempTable
UNION SELECT    2,'Mr.^Scott^A.^Mozisek^Sr.'
UNION SELECT    3,'Ms.^Jane^Q.^Doe^'
UNION SELECT    5,NULL
UNION SELECT    7,'^Betsy^^Ross^'
;

SELECT  SourceTable.*
        ,ChildTable.Col1        AS  ColTitle
        ,ChildTable.Col2        AS  ColFirst
        ,ChildTable.Col3        AS  ColMiddle
        ,ChildTable.Col4        AS  ColLast
        ,ChildTable.Col5        AS  ColSuffix
FROM    #TempTable              AS  SourceTable
OUTER APPLY dbo.fStringToPivotTable(SourceTable.Name,'^')       AS  ChildTable
;

No, I have not tested any plan (I just needed it to work). Oh, yeah: SQL Server 2012 (12.0 SP2)

Comments? Corrections? Enhancements?


回答1:


Here is my TVF. Easy to expand up to the 32 (the pattern is pretty clear).

This is a straight XML without the cost of the PIVOT.

Example - Notice the OUTER APPLY --- Use CROSS APPLY to Exclude NULLs

Select A.ID
      ,B.*
 From #TempTable A
 Outer Apply [dbo].[tvf-Str-Parse-Row](A.Name,'^') B

Returns

The UDF if Interested

CREATE FUNCTION [dbo].[tvf-Str-Parse-Row] (@String varchar(max),@Delimiter varchar(10))
Returns Table 
As
Return (
    Select Pos1 = ltrim(rtrim(xDim.value('/x[1]','varchar(max)')))
          ,Pos2 = ltrim(rtrim(xDim.value('/x[2]','varchar(max)')))
          ,Pos3 = ltrim(rtrim(xDim.value('/x[3]','varchar(max)')))
          ,Pos4 = ltrim(rtrim(xDim.value('/x[4]','varchar(max)')))
          ,Pos5 = ltrim(rtrim(xDim.value('/x[5]','varchar(max)')))
          ,Pos6 = ltrim(rtrim(xDim.value('/x[6]','varchar(max)')))
          ,Pos7 = ltrim(rtrim(xDim.value('/x[7]','varchar(max)')))
          ,Pos8 = ltrim(rtrim(xDim.value('/x[8]','varchar(max)')))
          ,Pos9 = ltrim(rtrim(xDim.value('/x[9]','varchar(max)')))
    From  (Select Cast('<x>' + replace((Select replace(@String,@Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml) as xDim) as A 
    Where @String is not null
)
--Thanks Shnugo for making this XML safe
--Select * from [dbo].[tvf-Str-Parse-Row]('Dog,Cat,House,Car',',')
--Select * from [dbo].[tvf-Str-Parse-Row]('John <test> Cappelletti',' ')


来源:https://stackoverflow.com/questions/46104428/tsql-sql-server-table-function-to-parse-split-delimited-string-to-multiple-sep

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!