【问题标题】:split comma separated values into distinct rows将逗号分隔的值拆分为不同的行
【发布时间】:2013-08-10 18:46:39
【问题描述】:

我有一个如下所示的表格:

id  fk_det  userid
3   9   name1,name2
6   1   name3
9   2   name4,name5
12  3   name6,name7

我已经学会后悔将用户 ID 的值用逗号分隔,所以我想将行拆分并最终得到类似的东西

id  fk_det  userid
3   9   name1
x   9   name2
6   1   name3
9   2   name4
x   2   name5
12  3   name6
x   3   name7

我一直在看这样的东西:

select fk_det, det, LEFT(userid, CHARINDEX(',',userid+',')-1),
    STUFF(userid, 1, CHARINDEX(',',userid+','), '')
from global_permissions

但我不确定当用户 ID 包含超过 2 个项目时如何使其工作(它可能,有些可能没有,有些可能有多个,只是取决于)

【问题讨论】:

标签: sql sql-server


【解决方案1】:

这是我倾向于使用的:

IF EXISTS (
    SELECT 1
    FROM dbo.sysobjects
    WHERE id = object_id(N'[dbo].[ParseString]')
        AND xtype in (N'FN', N'IF', N'TF'))
BEGIN
    DROP FUNCTION [dbo].[ParseString]
END
GO

CREATE FUNCTION dbo.ParseString (@String VARCHAR(8000), @Delimiter VARCHAR(10))
RETURNS TABLE
AS
/*******************************************************************************************************
*    dbo.ParseString
*
*    Creator:        magicmike
*    Date:           9/12/2006
*
*
*    Outline:        A set-based string tokenizer
*                    Takes a string that is delimited by another string (of one or more characters),
*                    parses it out into tokens and returns the tokens in table format.  Leading
*                    and trailing spaces in each token are removed, and empty tokens are thrown
*                    away.
*
*
*    Usage examples/test cases:
                Single-byte delimiter:
                     select * from dbo.ParseString2('|HDI|TR|YUM|||', '|')
                     select * from dbo.ParseString2('HDI| || TR    |YUM', '|')
                     select * from dbo.ParseString2(' HDI| || S P A C E S |YUM | ', '|')
                     select * from dbo.ParseString2('HDI|||TR|YUM', '|')
                     select * from dbo.ParseString2('', '|')
                     select * from dbo.ParseString2('YUM', '|')
                     select * from dbo.ParseString2('||||', '|')
                     select * from dbo.ParseString2('HDI TR YUM', ' ')
                     select * from dbo.ParseString2(' HDI| || S P A C E S |YUM | ', ' ') order by Ident
                     select * from dbo.ParseString2(' HDI| || S P A C E S |YUM | ', ' ') order by StringValue

                Multi-byte delimiter:
                     select * from dbo.ParseString2('HDI and TR', 'and')
                     select * from dbo.ParseString2('Pebbles and Bamm Bamm', 'and')
                     select * from dbo.ParseString2('Pebbles and sandbars', 'and')
                     select * from dbo.ParseString2('Pebbles and sandbars', ' and ')
                     select * from dbo.ParseString2('Pebbles and sand', 'and')
                     select * from dbo.ParseString2('Pebbles and sand', ' and ')
*
*
*    Notes:
                     1. A delimiter is optional.  If a blank delimiter is given, each byte is returned in it's own row (including spaces).
                        select * from dbo.ParseString3('|HDI|TR|YUM|||', '')
                     2. In order to maintain compatibility with SQL 2000, ident is not sequential but can still be used in an order clause
                     If you are running on SQL2005 or later
                        SELECT Ident, StringValue FROM
                     with
                        SELECT Ident = ROW_NUMBER() OVER (ORDER BY ident), StringValue FROM
*
*
*    Modifications
*
*
********************************************************************************************************/
RETURN (
SELECT Ident, StringValue FROM
    (
        SELECT Num as Ident,
            CASE
                WHEN DATALENGTH(@delimiter) = 0 or @delimiter IS NULL
                    THEN LTRIM(SUBSTRING(@string, num, 1)) --replace this line with '' if you prefer it to return nothing when no delimiter is supplied. Remove LTRIM if you want to return spaces when no delimiter is supplied
            ELSE
                LTRIM(RTRIM(SUBSTRING(@String,
                    CASE
                        WHEN (Num = 1 AND SUBSTRING(@String,num ,DATALENGTH(@delimiter)) <> @delimiter) THEN 1
                        ELSE Num + DATALENGTH(@delimiter)
                    END,
                    CASE CHARINDEX(@Delimiter, @String, Num + DATALENGTH(@delimiter))
                        WHEN 0 THEN LEN(@String) - Num + DATALENGTH(@delimiter)
                        ELSE CHARINDEX(@Delimiter, @String, Num + DATALENGTH(@delimiter)) - Num -
                            CASE
                                WHEN Num > 1 OR (Num = 1 AND SUBSTRING(@String,num ,DATALENGTH(@delimiter)) = @delimiter)
                                       THEN DATALENGTH(@delimiter)
                                ELSE 0
                            END
                       END
                    )))
              End  AS StringValue
        FROM dbo.Numbers
        WHERE Num <= LEN(@String)
            AND (
                    SUBSTRING(@String, Num, DATALENGTH(ISNULL(@delimiter,''))) = @Delimiter
                    OR Num = 1
                    OR DATALENGTH(ISNULL(@delimiter,'')) = 0
                )
    ) R WHERE StringValue <> ''
)

你会这样使用它:

SELECT id, pk_det, V.StringValue as userid
FROM myTable T
OUTER APPLY dbo.ParseString(T.userId) V

UDF 需要一个假定以下架构的“计数”或数字表:

IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'Numbers')
BEGIN

     CREATE TABLE dbo.Numbers
    (
        Num INT NOT NULL 
        CONSTRAINT [PKC__Numbers__Num] PRIMARY KEY CLUSTERED (Num) on [PRIMARY]
    )
    ;WITH Nbrs_3( n ) AS ( SELECT 1 UNION SELECT 0 ),
          Nbrs_2( n ) AS ( SELECT 1 FROM Nbrs_3 n1 CROSS JOIN Nbrs_3 n2 ),
          Nbrs_1( n ) AS ( SELECT 1 FROM Nbrs_2 n1 CROSS JOIN Nbrs_2 n2 ),
          Nbrs_0( n ) AS ( SELECT 1 FROM Nbrs_1 n1 CROSS JOIN Nbrs_1 n2 ),
          Nbrs  ( n ) AS ( SELECT 1 FROM Nbrs_0 n1 CROSS JOIN Nbrs_0 n2 )

    INSERT INTO dbo.Numbers(Num)
    SELECT n
    FROM ( SELECT ROW_NUMBER() OVER (ORDER BY n)
          FROM Nbrs ) D ( n )
         WHERE n <= 50000 ;
END

数字表格是您工具集的宝贵补充。引用 Adam Machanic 的话:

数字表确实非常宝贵。我一直使用它们 字符串操作,模拟窗口函数,填充测试 具有大量数据的表,消除游标逻辑和许多其他 没有它们,任务将变得异常困难。

使用数字表是否像我看到的一些人所说的那样是一种黑客行为? 不。告诉我另一种有效地做所有事情的方法 桌子可以。会浪费空间吗?不,下面的脚本会用完 每个数据库中大约有 900 KB 的磁盘空间。那绝对是 没有什么。你最终会得到数百万甚至数十亿倍 磁盘空间投资在易于开发和时间方面的回报 已保存。

http://dataeducation.com/you-require-a-numbers-table/

【讨论】:

    【解决方案2】:

    试试这个:)

    DECLARE @Name TABLE
        (
          id INT NULL ,
          fk_det INT NULL ,
          userid NVARCHAR(100) NULL
        )
    
    INSERT INTO @Name
                          ( id,  fk_det, userid)
    VALUES     (3,9,'name1,name2'  )
    
    INSERT INTO @Name
                          ( id,  fk_det, userid)
    VALUES     (6,1,'name3'  )
    
    INSERT INTO @Name
                          ( id,  fk_det, userid)
    VALUES     (9,2,'name4,name5'  )
    
    INSERT INTO @Name
                          ( id,  fk_det, userid)
    VALUES     (12,3,'name6,name7'  )
    
    SELECT  *
    FROM    @Name
    
     SELECT id,A.fk_det,  
         Split.a.value('.', 'VARCHAR(100)') AS String  
     FROM  (SELECT id,fk_det,  
             CAST ('<M>' + REPLACE(userid, ',', '</M><M>') + '</M>' AS XML) AS String  
         FROM  @Name) AS A CROSS APPLY String.nodes ('/M') AS Split(a);  
    

    【讨论】:

    • 简单实用。在某些情况下,将需要 TRIM 函数,因为逗号值往往由“,”等分隔。
    【解决方案3】:

    作为随处可见的标准 sproc 调用的替代方案:

    with temp as(
    select id,fk_det,cast('<comma>'+replace(userid,',','</comma><comma>')+'</comma>' as XMLcomma
    from global_permissions
    )
    
    select id,fk_det,a.value('comma[1]','varchar(512)')
    cross apply temp.XMLcomma.nodes('/comma') t(a)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-28
      • 2019-09-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多