【问题标题】:Using string_split to create rows from multiple columns使用 string_split 从多列创建行
【发布时间】:2018-09-14 06:54:35
【问题描述】:

我的数据看起来像这个例子(不幸的是,规模要大得多):

+----+-------+--------------------+-----------------------------------------------+
| ID | Data  | Cost               | Comments                                      |
+----+-------+--------------------+-----------------------------------------------+
| 1  | 1|2|3 | $0.00|$3.17|$42.42 | test test||previous thing has a blank comment |
+----+-------+--------------------+-----------------------------------------------+
| 2  | 1     | $420.69            | test                                          |
+----+-------+--------------------+-----------------------------------------------+
| 3  | 1|2   | $3.50|$4.20        | |test                                         |
+----+-------+--------------------+-----------------------------------------------+

我所拥有的表中的某些列是管道分隔的,但它们在每一行中都是一致的。所以每个分隔值对应于同一行的其他列中的相同索引。

所以我可以做这样的事情,这是我想要的单列:

SELECT ID, s.value AS datavalue
FROM MyTable t CROSS APPLY STRING_SPLIT(t.Data, '|') s

这会给我这个:

+----+-----------+
| ID | datavalue |
+----+-----------+
| 1  | 1         |
+----+-----------+
| 1  | 2         |
+----+-----------+
| 1  | 3         |
+----+-----------+
| 2  | 1         |
+----+-----------+
| 3  | 1         |
+----+-----------+
| 3  | 2         |
+----+-----------+

但我也想获取其他列(本例中为 cost 和 cmets),以便相应的项目都在同一行中,如下所示:

+----+-----------+-----------+------------------------------------+
| ID | datavalue | costvalue | commentvalue                       |
+----+-----------+-----------+------------------------------------+
| 1  | 1         | $0.00     | test test                          |
+----+-----------+-----------+------------------------------------+
| 1  | 2         | $3.17     |                                    |
+----+-----------+-----------+------------------------------------+
| 1  | 3         | $42.42    | previous thing has a blank comment |
+----+-----------+-----------+------------------------------------+
| 2  | 1         | $420.69   | test                               |
+----+-----------+-----------+------------------------------------+
| 3  | 1         | $3.50     |                                    |
+----+-----------+-----------+------------------------------------+
| 3  | 2         | $4.20     | test                               |
+----+-----------+-----------+------------------------------------+

我不确定实现这一目标的最佳或最简单的方法是什么

【问题讨论】:

    标签: sql sql-server split


    【解决方案1】:

    STRING_SPLIT 无法实现这一点,因为 Microsoft 拒绝将序数位置作为结果集的一部分提供。因此,您需要使用不同的功能。就个人而言,我推荐 Jeff Moden 的DelimitedSplit8k

    然后,您可以这样做:

    CREATE TABLE #Sample (ID int,
                          [Data] varchar(200),
                          Cost  varchar(200),
                          Comments varchar(8000));
    GO
    INSERT INTO #Sample
    VALUES (1,'1|2|3','$0.00|$3.17|$42.42','test test||previous thing has a blank comment'),
           (2,'1','$420.69','test'),
           (3,'1|2','$3.50|$4.20','|test');
    
    GO
    SELECT S.ID,
           DSd.Item AS DataValue,
           DSc.Item AS CostValue,
           DSct.Item AS CommentValue
    FROM #Sample S
         CROSS APPLY dbo.DelimitedSplit8K(S.[Data],'|')  DSd
         CROSS APPLY (SELECT *
                      FROM DelimitedSplit8K(S.Cost,'|') SS
                      WHERE SS.ItemNumber = DSd.ItemNumber) DSc
         CROSS APPLY (SELECT *
                      FROM DelimitedSplit8K(S.Comments,'|') SS
                      WHERE SS.ItemNumber = DSd.ItemNumber) DSct;
    
    GO
    DROP TABLE #Sample;
    GO
    

    然而,这个问题只有一个正确的答案:不要在 SQL Server 中存储分隔值。 以标准化的方式存储它们,你就不会遇到这个问题。

    【讨论】:

    • 序号位置非常关键。 Jeff Moden 是唯一一个包含它的可行分离器,这总是让我感到震惊。大多数 XML 拆分器至少以相同的顺序返回它们,但我更喜欢明确而不是假设它有效。
    • 是的,使用这样的数据并不太有趣。有一个 API 基本上以这种方式为我们提供数据。将更多地检查此解决方案。谢谢! (可能最终会从数据库中提取所有数据并用脚本将其拆分以使其正确插入)
    • @SeanLange 老实说,我希望 MS 用 2017 年更新该功能以提供该职位。当他们不这样做时,我实际上对他们感到非常失望,因为社区对功能的需求非常高。
    【解决方案2】:

    这是一种使用递归 CTE 而不是用户定义函数 (UDF) 的解决方法,这对于那些没有创建函数权限的人很有用。

    CREATE TABLE mytable(
       ID       INTEGER  NOT NULL PRIMARY KEY 
      ,Data     VARCHAR(7) NOT NULL
      ,Cost     VARCHAR(20) NOT NULL
      ,Comments VARCHAR(47) NOT NULL
    );
    INSERT INTO mytable(ID,Data,Cost,Comments) VALUES (1,'1|2|3','$0.00|$3.17|$42.42','test test||previous thing has a blank comment');
    INSERT INTO mytable(ID,Data,Cost,Comments) VALUES (2,'1','$420.69','test');
    INSERT INTO mytable(ID,Data,Cost,Comments) VALUES (3,'1|2','$3.50|$4.20','|test');
    

    此查询允许通过使用变量来选择分隔符,然后使用公用表表达式解析每个分隔字符串,为这些字符串的每个部分生成一行,并保留每个部分的序数位置。

    declare @delimiter as varchar(1)
    set @delimiter = '|'
    
    ;with cte as (
          select id
               , convert(varchar(max), null) as datavalue
               , convert(varchar(max), null) as costvalue
               , convert(varchar(max), null) as commentvalue
               , convert(varchar(max), data + @delimiter) as data
               , convert(varchar(max), cost + @delimiter) as cost
               , convert(varchar(max), comments + @delimiter) as comments
          from mytable as t
          union all
          select id
               , convert(varchar(max), left(data, charindex(@delimiter, data) - 1))
               , convert(varchar(max), left(cost, charindex(@delimiter, cost) - 1))
               , convert(varchar(max), left(comments, charindex(@delimiter, comments) - 1))
               , convert(varchar(max), stuff(data, 1, charindex(@delimiter, data), ''))
               , convert(varchar(max), stuff(cost, 1, charindex(@delimiter, cost), ''))
               , convert(varchar(max), stuff(comments, 1, charindex(@delimiter, comments), ''))
          from cte
          where (data like ('%' + @delimiter + '%') and cost like ('%' + @delimiter + '%')) or comments like ('%' + @delimiter + '%')
         )
    select id, datavalue, costvalue, commentvalue
    from cte
    where datavalue IS NOT NULL
    order by id, datavalue
    

    当递归添加新行时,它使用 left() 将分隔字符串的第一部分放入所需的输出列中,然后使用 stuff() 从源字符串中删除最后使用的分隔符,以便下一个行将从下一个分隔符开始。请注意,为了启动提取,分隔符被添加到源分隔字符串的末尾,以确保 where 子句不排除任何想要的字符串。

    结果:

      id   datavalue   costvalue              commentvalue             
     ---- ----------- ----------- ------------------------------------ 
       1           1   $0.00       test test                           
       1           2   $3.17                                           
       1           3   $42.42      previous thing has a blank comment  
       2           1   $420.69     test                                
       3           1   $3.50                                           
       3           2   $4.20       test                
    

    证明here at dbfiddle.uk

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-08
      • 1970-01-01
      • 2013-01-07
      • 1970-01-01
      • 1970-01-01
      • 2014-08-24
      相关资源
      最近更新 更多