2017-10-10 76 views
0

我有一个数据源,其中包含SQL Server中分段区域中存在的分隔字段中的数据。我想将这些数据转换成多行,因此更容易处理。这不同于类似主题的众多其他问题和答案,因为我有多个字段,其中存在分隔数据。以下是对我的数据看起来像一个例子:将多个分隔字段转换为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 

回答

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是它如何工作的例子。

+0

嗯...您的解决方案似乎是有点马车:它返回的ID,但无论是场也不值...最肯定的是因为你在第一次迭代中选择NULL,然后在下一次选择NULL的子串。 – Tyron78

+0

完美。也增加了一个工作版本。 – Tyron78

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 

如果可能的话,最好的办法是看你能不能改变它是什么工艺是填充您的源数据,因此被归一化,而不是划定,因为它是一个痛苦的工作。

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)