MS SQL Server获取逗号之间的值

时间:2018-01-13 13:38:10

标签: sql sql-server sql-server-2008 tsql

我在Table1中有一个列,其中的字符串由commma分隔:

Id Val
1  ,4
2  ,3,1,0
3  NULL
4  ,5,2

是否有一种简单的方法可以从该列中拆分并获取任何值, 例如

SELECT值(1)FROM Table1应该得到

Id Val
1  4
2  3
3  NULL
4  5

SELECT值(2)FROM Table1应该得到

Id Val
1  NULL
2  1
3  NULL
4  2

谢谢!

5 个答案:

答案 0 :(得分:2)

在列中存储逗号分隔值总是很麻烦,请考虑更改表结构

要完成此操作,请创建一个拆分字符串函数。这是将字符串拆分为单个行的最佳方法之一。参考http://www.sqlservercentral.com/articles/Tally+Table/72993/

CREATE FUNCTION [dbo].[DelimitedSplit8K]
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000...
     -- enough to cover NVARCHAR(4000)
  WITH E1(N) AS (
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
                ),                          --10E+1 or 10 rows
       E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
       E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
 cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
                     -- for both a performance gain and prevention of accidental "overruns"
                 SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                ),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
                 SELECT 1 UNION ALL
                 SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
                ),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
                 SELECT s.N1,
                        ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
                   FROM cteStart s
                )
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
 SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
        Item       = SUBSTRING(@pString, l.N1, l.L1)
   FROM cteLen l

调用函数

SELECT *
FROM   yourtable
       CROSS apply (SELECT CASE WHEN LEFT(val, 1) = ',' THEN Stuff(val, 1, 1, '') ELSE val END) cs (cleanedval)
       CROSS apply [dbo].[Delimitedsplit8k](cs.cleanedval, ',')
WHERE  ItemNumber = 1

SELECT *
FROM   yourtable
       CROSS apply (SELECT CASE WHEN LEFT(val, 1) = ',' THEN Stuff(val, 1, 1, '') ELSE val END) cs (cleanedval)
       CROSS apply [dbo].[Delimitedsplit8k](cs.cleanedval, ',')
WHERE  ItemNumber = 2 

答案 1 :(得分:1)

使用递归CTE尝试此逻辑

DECLARE @Pos INT = 2
DECLARE @T TABLE
(
    Id INT,
    Val VARCHAR(50)
)

INSERT INTO @T
VALUES(1,',4'),(2,',3,1,0'),(3,NULL),(4,',5,2')

;WITH CTE
AS
(
    SELECT
        Id,
        SeqNo = 0,
        MyStr = SUBSTRING(Val,CHARINDEX(',',Val)+1,LEN(Val)),
        Num = REPLACE(SUBSTRING(Val,1,CHARINDEX(',',Val)),',','')
        FROM @T

    UNION ALL

    SELECT
        Id,
        SeqNo = SeqNo+1,
        MyStr = CASE WHEN CHARINDEX(',',MyStr)>0
                    THEN SUBSTRING(MyStr,CHARINDEX(',',MyStr)+1,LEN(MyStr))
                ELSE NULL END,
        Num = CASE WHEN CHARINDEX(',',MyStr)>0
                    THEN REPLACE(SUBSTRING(MyStr,1,CHARINDEX(',',MyStr)),',','')
                ELSE MyStr END
        FROM CTE
            WHERE ISNULL(REPLACE(MyStr,',',''),'')<>''
)   
SELECT
    T.Id,
    CTE.Num
    FROM @T t 
        LEFT JOIN CTE
            ON T.Id = cte.Id
                AND SeqNo = @Pos

以上

的输出

enter image description here

答案 2 :(得分:1)

使用Parse / Split函数和OUTER APPLY的另一个选项

示例

Declare @YourTable Table ([Id] int,[Val] varchar(50))
Insert Into @YourTable Values 
 (1,',4')
,(2,',3,1,0')
,(3,NULL)
,(4,',5,2')

Select A.ID
      ,Val  =  B.RetVal
 From @YourTable A
 Outer Apply (
                Select * From [dbo].[tvf-Str-Parse](A.Val,',')
                 Where RetSeq = 2
              ) B

<强>返回

ID  Val
1   4
2   3
3   NULL
4   5

感兴趣的UDF

CREATE FUNCTION [dbo].[tvf-Str-Parse] (@String varchar(max),@Delimiter varchar(10))
Returns Table 
As
Return (  
    Select RetSeq = Row_Number() over (Order By (Select null))
          ,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
    From  (Select x = Cast('<x>' + replace((Select replace(@String,@Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A 
    Cross Apply x.nodes('x') AS B(i)
);

答案 3 :(得分:1)

测试数据

Declare @t TABLE (Id INT ,  Val VARCHAR(100))
INSERT INTO @t  VALUES
(1  , '4'),
(2  , '3,1,0'),
(3   , NULL),
(4  , '5,2')

功能定义

CREATE FUNCTION [dbo].[fn_xml_Splitter]
(
      @delimited nvarchar(max)
    , @delimiter nvarchar(1)
    , @Position  INT            = NULL
) 
RETURNS TABLE
AS
RETURN
    (
        SELECT Item
        FROM (
                SELECT   Split.a.value('.', 'VARCHAR(100)') Item
                        , ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) ItemNumber
                FROM   
                    (SELECT Cast ('<X>' + Replace(@delimited, @delimiter, '</X><X>') 
                                    + '</X>' AS XML) AS Data
                    ) AS t CROSS APPLY Data.nodes ('/X') AS Split(a) 
            )x
        WHERE x.ItemNumber = @Position OR @Position IS NULL
    );
GO

函数调用

现在您可以通过两种不同的方式调用此函数。

1。要在特定位置返回Item,请在函数的第3个参数中指定位置:

SELECT * 
FROM @t t
 CROSS APPLY [dbo].[fn_xml_Splitter](t.Val , ',', 1) 

2。要返回所有项目,请在函数的第3个参数中指定关键字DEFUALT

SELECT * 
FROM @t t
 CROSS APPLY [dbo].[fn_xml_Splitter](t.Val , ',', DEFAULT)

答案 4 :(得分:1)

以下是使用CTE并将CSV转换为XML的示例:

DECLARE @Test TABLE (
    CsvData VARCHAR(10)
);

INSERT INTO @Test (CsvData)
VALUES
    ('1,2,3'),
    (',4,5,7'),
    (NULL),
    (',3,');

WITH XmlData AS (
    SELECT CONVERT(XML, '<val>' + REPLACE(CsvData, ',', '</val><val>') + '</val>') [CsvXml]
    FROM @Test
)
SELECT xd.CsvXml.value('val[2]', 'VARCHAR(10)')
FROM XmlData xd;

这将输出:

2
4
NULL
3

要显示的列由XPath查询控制。在这种情况下,val[2]

这里的主要优点是不需要用户定义的功能。