将多个分隔字段转换为SQL Server

时间:2017-10-10 15:20:02

标签: sql sql-server delimited

我有一个数据源,其中包含分隔字段中的数据,这些字段存在于SQL Server的暂存区域中。我想将这些数据转换为多行,以便更容易使用。这与类似主题的许多其他问题和答案不同,因为我有多个字段,其中存在此​​分隔数据。以下是我的数据的示例:

 ID | Field | Value
 ---+-------+------
 1  | a,b,c | 1,2,3
 2  | a,c   | 5,2

这是理想的输出:

ID | Field | Value
---+-------+------    
1  | a     | 1
1  | b     | 2
1  | c     | 3
2  | a     | 5
2  | c     | 2

到目前为止,我的代码使用的XML解析方法与此处提到的方法类似:Turning a Comma Separated string into individual rows我需要扩展它以将每个字段连接到其相应的值,这是通过为每个ID生成row_number然后匹配来完成的基于ID和此row_number。

我的问题是它很慢,所以我想知道是否有人有更高效的方法?

select      
    [Value].ID, [Field], [Value]
from        
    (select
         A.ID, Split.a.value('.', 'varchar(100)') as [Value],
         row_number() over (partition by ID order by Split.a) as RowNumber
     from 
         (select 
              ID, cast('<M>' + replace([Value], ',', '</M><M>') + '</M>' as xml) as [Value]
          from
              #source_table
          where
              [Field] not like '%[<>&%]%' and [Value] not like '%[<>&%]%') as A
     cross apply
         [Value].nodes ('/M') as Split(a)
    ) [Value]
inner join  
    (
        select
            A.ID, Split.a.value('.', 'varchar(100)') as [Field],
            row_number() over (partition by A.ID order by Split.a) as RowNumber
        from 
            (select
                 ID, cast('<M>' + replace([Field], ',', '</M><M>') + '</M>' as xml) as [Field]
             from  
                 #source_table
             where
                 [Field] not like '%[<>&%]%' and [Value] not like '%[<>&%]%') as A
        cross apply 
            [Field].nodes ('/M') as Split(a)
    ) [Field] on [Value].ID = [Field].ID and [Value].RowNumber = [Field].RowNumber

3 个答案:

答案 0 :(得分:1)

这是使用Jeff Moden的分离器的方法。 http://www.sqlservercentral.com/articles/Tally+Table/72993/该分割器的一个不错的功能是它返回每个元素的序号位置,以便您可以将它用于连接等。

从一些数据开始。

declare @Something table
(
    ID int
    , Field varchar(50)
    , Value varchar(50)
)

insert @Something values
(1, 'a,b,c', '1,2,3')
, (2, 'a,c', '5,2')
;

由于您有两组分隔数据,因此您将被迫为每组分隔值拆分此数据。以下是如何利用此拆分器来实现此目的。

with Fields as
(
    select *
    from @Something s
    cross apply dbo.DelimitedSplit8K(s.Field, ',') f
)
, Value as
(
    select *
    from @Something s
    cross apply dbo.DelimitedSplit8K(s.Value, ',') v
)

select f.ID
    , Field = f.Item
    , Value = v.Item
from Fields f
join Value v on v.ItemNumber = f.ItemNumber and v.ID = f.ID

如果可能的话,最好是看看你是否可以改变填充你的源数据的任何过程,这样它就会被标准化而不是分隔,因为它很难处理。

答案 1 :(得分:0)

一种方法是递归CTE:

with cte as (
      select id, cast(NULL as varchar(max)) as field, cast(NULL as varchar(max)) as value, field as field_list, value as value_list, 0 as lev
      from t
      union all
      select id, left(field_list, charindex(',', field_list + ',') - 1),
             left(value_list, charindex(',', value_list + ',') - 1),
             substring(field_list, charindex(',', field_list + ',') + 1, len(field_list)),
             substring(value_list, charindex(',', value_list + ',') + 1, len(value_list)),
             1 + lev
      from cte
      where field_list <> '' and value_list <> ''
     )
select *
from cte
where lev > 0;

Here就是它如何运作的一个例子。

答案 2 :(得分:0)

基于@Gordon Linoff在此查询另一个递归cte:

DECLARE @t TABLE(
  ID int
  ,Field VARCHAR(MAX)
  ,Value VARCHAR(MAX)
)

INSERT INTO @t VALUES
(1, 'a,b,c', '1,2,3')
,(2, 'a,c', '5,2')
,(3, 'x', '7');


with cte as (
      select ID
            ,SUBSTRING(Field, 1, CASE WHEN CHARINDEX(',', Field) > 0 THEN CHARINDEX(',', Field)-1 ELSE LEN(Field) END) AS Field
            ,SUBSTRING(Value, 1, CASE WHEN CHARINDEX(',', Value) > 0 THEN CHARINDEX(',', Value)-1 ELSE LEN(Value) END) AS Value
            ,SUBSTRING(Field, CASE WHEN CHARINDEX(',', Field) > 0 THEN CHARINDEX(',', Field)+1 ELSE 1 END, LEN(Field)-CASE WHEN CHARINDEX(',', Field) > 0 THEN CHARINDEX(',', Field) ELSE 0 END) as field_list
            ,SUBSTRING(Value, CASE WHEN CHARINDEX(',', Value) > 0 THEN CHARINDEX(',', Value)+1 ELSE 1 END, LEN(Value)-CASE WHEN CHARINDEX(',', Value) > 0 THEN CHARINDEX(',', Value) ELSE 0 END) as value_list
            ,0 as lev
      from @t
      WHERE CHARINDEX(',', Field) > 0
      UNION ALL
      select ID
            ,SUBSTRING(field_list, 1, CASE WHEN CHARINDEX(',', field_list) > 0 THEN CHARINDEX(',', field_list)-1 ELSE LEN(field_list) END) AS Field
            ,SUBSTRING(value_list, 1, CASE WHEN CHARINDEX(',', value_list) > 0 THEN CHARINDEX(',', value_list)-1 ELSE LEN(value_list) END) AS Value
            ,CASE WHEN CHARINDEX(',', field_list) > 0 THEN SUBSTRING(field_list, CHARINDEX(',', field_list)+1, LEN(field_list)-CHARINDEX(',', field_list)) ELSE '' END as field_list
            ,CASE WHEN CHARINDEX(',', value_list) > 0 THEN SUBSTRING(value_list, CHARINDEX(',', value_list)+1, LEN(value_list)-CHARINDEX(',', value_list)) ELSE '' END as value_list
            ,lev + 1
      from cte
      WHERE LEN(field_list) > 0
     )
select ID, Field, Value
from cte
UNION ALL
SELECT ID, Field, Value
  FROM @t
  WHERE CHARINDEX(',', Field) = 0
ORDER BY ID, Field
OPTION (MAXRECURSION 0)