2009-03-31 6 views
1

J'ai une table qui ressemble à ceci:Comment développer des valeurs séparées par des virgules dans des lignes séparées à l'aide de SQL Server 2005?

ProductId, Color 
"1", "red, blue, green" 
"2", null 
"3", "purple, green" 

Et je veux l'étendre à ceci:

ProductId, Color 
1, red 
1, blue 
1, green 
2, null 
3, purple 
3, green 

Quelle est la meilleure façon d'y arriver? Est-il possible sans boucle dans un proc?

+0

donner ma méthode un essai, il sera beaucoup plus rapide ... –

Répondre

9

Jetez un coup d'œil à cette fonction. J'ai fait des trucs similaires pour diviser et transposer des données dans Oracle. Boucle sur les données en insérant les valeurs décodées dans une table temporaire. Le principe du couvent est que MS vous laisse le faire à la volée, tandis qu'Oracle requiert une table temporaire explicite.

MS SQL Split Function
Better Split Function

Modifier par auteur: Cela a très bien fonctionné. Le code final ressemblait à ceci (après avoir créé la fonction split):

select pv.productid, colortable.items as color 
from product p 
    cross apply split(p.color, ',') as colortable 
+2

ces boucles seront lents, je serais prêt à parier de l'argent que ma requête serait fumer un jour ... –

+0

Pour SQL 2016, vous pouvez utiliser la fonction intégrée: 'CROSS APPLY STRING_SPLIT (p.color, ',')' – Dave

0

Fixez votre base de données si possible. Les listes délimitées par des virgules dans les cellules de base de données indiquent un schéma défectueux 99% du temps ou plus.

+1

Je suis d'accord, mais cela n'aide pas vraiment. – TheSoftwareJedi

+0

l'ensemble de résultats demandé est une meilleure conception, il suffit de mettre "INTO YourNewTableName" entre la liste de sélection et le FROM de la requête et une nouvelle table sera créée, où les couleurs sont divisées. –

+3

Comment proposez-vous l'affiche réparer sa base de données sans la réponse à cette question? -1 – brian

0

Je crée une fonction définie de table CLR pour cela:

http://msdn.microsoft.com/en-us/library/ms254508(VS.80).aspx

La raison est que le code CLR va être beaucoup mieux à l'analyse syntaxique à part les chaînes (travail de calcul) et peut transmettre cette information en tant qu'ensemble, ce à quoi SQL Server est très bon (gestion des ensembles).

La fonction CLR renvoie une série d'enregistrements en fonction des valeurs analysées (et de la valeur de l'ID d'entrée).

Vous utiliserez ensuite une application CROSS sur chaque élément de votre table.

+0

IMHO qui est exagéré pour quelque chose d'aussi trivial. – James

+0

@James: C'est vraiment simple, BEAUCOUP plus simple que le code que vous avez à parcourir pour analyser les mouvements dans T-SQL, et ça va toujours être plus rapide à l'analyse. La croix s'applique est le choix naturel ici une fois que vous avez une fonction qui analyse les lignes à part. – casperOne

+0

en fonction du nombre ou des lignes à traiter et de la longueur des couleurs CSV, un CLR ne sera pas mis à l'échelle. Cela fonctionnera correctement dans cet exemple de 3 lignes, mais si vous devez exécuter cette requête tous les jours, cela sera lent. Une requête SQL pure comme la mienne sera beaucoup plus rapide. –

4

Vous pouvez essayer cela, ne marche pas besoin des fonctions supplémentaires:

 
declare @t table (col1 varchar(10), col2 varchar(200)) 
insert @t 
      select '1', 'red,blue,green' 
union all select '2', NULL 
union all select '3', 'green,purple' 


select col1, left(d, charindex(',', d + ',')-1) as e from (
    select *, substring(col2, number, 200) as d from @t col1 left join 
     (select distinct number from master.dbo.spt_values where number between 1 and 200) col2 
     on substring(',' + col2, number, 1) = ',') t 
+0

GRANDE RÉPONSE, c'est une bien meilleure requête que mon premier essai. Voir ma réponse pour une version modifiée de ceci. Votre utilisation de valeurs d'alias de table qui sont identiques aux noms de colonnes était déroutante et l'utilisation d'une table système pour les nombres vous oblige à utiliser une table dérivée supplémentaire. A part ça, GREAT JOB! –

5

basé sur vos tables:

create table test_table 
(
    ProductId int 
    ,Color  varchar(100) 
) 

insert into test_table values (1, 'red, blue, green') 
insert into test_table values (2, null) 
insert into test_table values (3, 'purple, green') 

créer une nouvelle table comme ceci:

CREATE TABLE Numbers 
(
    Number int not null primary key 
) 

que a des lignes contenant des valeurs de 1 à 8000 ou plus.

ce retournera ce que vous voulez:

EDIT
ici est une bien meilleure requête, légèrement modifiée de la grande réponse de @Christopher Klein:

J'ai ajouté le "LTRIM()" les espaces de la liste de couleurs seraient donc gérés correctement: "rouge, bleu, vert". Sa solution ne nécessite aucun espace "rouge, bleu, vert". De plus, je préfère utiliser ma propre table Number et ne pas utiliser master.dbo.spt_values, cela permet également de supprimer une table dérivée.

SELECT 
    ProductId, LEFT(PartialColor, CHARINDEX(',', PartialColor + ',')-1) as SplitColor 
    FROM (SELECT 
       t.ProductId, LTRIM(SUBSTRING(t.Color, n.Number, 200)) AS PartialColor 
       FROM test_table    t 
        LEFT OUTER JOIN Numbers n ON n.Number<=LEN(t.Color) AND SUBSTRING(',' + t.Color, n.Number, 1) = ',' 
     ) t 

EDIT FIN

SELECT 
    ProductId, Color --,number 
    FROM (SELECT 
       ProductId 
        ,CASE 
         WHEN LEN(List2)>0 THEN LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(',', List2, number+1)-number - 1))) 
         ELSE NULL 
        END AS Color 
        ,Number 
       FROM (
         SELECT ProductId,',' + Color + ',' AS List2 
          FROM test_table 
        ) AS dt 
        LEFT OUTER JOIN Numbers n ON (n.Number < LEN(dt.List2)) OR (n.Number=1 AND dt.List2 IS NULL) 
       WHERE SUBSTRING(List2, number, 1) = ',' OR List2 IS NULL 
     ) dt2 
    ORDER BY ProductId, Number, Color 

ici est mon jeu de résultats:

ProductId Color 
----------- -------------- 
1   red 
1   blue 
1   green 
2   NULL 
3   purple 
3   green 

(6 row(s) affected) 

qui est le même ordre que vous voulez ...

+0

Cela a fait exactement ce dont j'avais besoin. Dans ma situation, je traitais des données qui avaient été combinées (plusieurs ordres de travail qui avaient été exécutés sous le même élément de ligne facturable, j'avais besoin de les séparer tout en conservant le numéro de poste). – mounty

0

Il suffit de convertir vos colonnes en XML et l'interroger. Voici un exemple.

select 
    a.value('.', 'varchar(42)') c 
from (select cast('<r><a>' + replace(@CSV, ',', '</a><a>') + '</a></r>' as xml) x) t1 
cross apply x.nodes('//r/a') t2(a) 
Questions connexes