2017-10-10 4 views
0

J'ai une source de données qui contient des données dans des champs délimités qui existent dans une zone de transfert dans SQL Server. Je voudrais transformer ces données en plusieurs lignes afin qu'il soit plus facile de travailler avec. Cela diffère des nombreuses autres questions et réponses sur des sujets similaires en ce sens que j'ai plusieurs champs où ces données délimitées existent. Voici un exemple de ce que mes données ressemble à:Conversion de plusieurs champs délimités en lignes dans SQL Server

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

Et ceci est la sortie désirée:

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

Mon code si utilise beaucoup la méthode d'analyse XML comme celui mentionné ici: Turning a Comma Separated string into individual rows je avais besoin pour l'étendre afin de joindre chaque champ à sa valeur correspondante, ce que j'ai fait en générant un row_number pour chaque ID, puis en faisant une correspondance en fonction de l'ID et de ce row_number. Mon problème est que c'est douloureusement lent, alors je me suis demandé si quelqu'un avait des méthodes plus performantes?

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 

Répondre

0

Une méthode est un CTE récursive:

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 est un exemple de la façon dont cela fonctionne.

+0

Hm ... votre solution semble être un peu bogué: il renvoie les ID, mais ni les champs, ni les valeurs ... le plus certainement parce que vous sélectionnez NULL dans la première itération, puis sous-chaîne de NULL dans le prochain ... – Tyron78

+0

Parfait. Ajout d'une version de travail aussi. – Tyron78

1

Voici une approche utilisant le séparateur de Jeff Moden. http://www.sqlservercentral.com/articles/Tally+Table/72993/ Une caractéristique intéressante de ce séparateur est qu'il renvoie la position ordinale de chaque élément afin que vous puissiez l'utiliser pour les jointures et autres.

À partir de quelques données.

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') 
; 

Puisque vous avez deux ensembles de données délimitées, vous serez obligé de le diviser pour chaque ensemble de valeurs délimitées. Voici comment vous pouvez tirer parti de ce séparateur pour accomplir ceci.

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 

Si possible, il serait préférable de voir si vous pouvez changer quelque procédé que c'est qui peuplait vos données source de sorte qu'il est normalisé et non délimité, car il est une douleur à travailler avec.

0

sur la requête Fonder de @Gordon Linoff ici une autre cte récursive:

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)