2010-11-04 6 views
1

J'ai une table de personnes qui contient un champ de code d'erreur qui peut contenir plusieurs codes d'erreur (001, 002, 003 ...). Je sais que c'est un problème de schéma mais c'est une application de fournisseur et je n'ai aucun contrôle sur le schéma, donc je dois travailler avec ce que j'ai.Comment faire pour joindre à une table qui a plusieurs valeurs dans la colonne?

Il existe également une table Error qui contient ErrorCode (char (3)) et descript (char (1000)). Dans ma requête, le Person.ErrorCode est joint au Error.ErrorCode pour obtenir la valeur de la description correspondante.

Pour les enregistrements de personne où il n'y a qu'un seul code d'erreur, je peux obtenir le Descript correspondant sans problème. Ce que j'essaie de faire est de concaténer les valeurs Descript pour les enregistrements où il y a plusieurs erreurs.

Par exemple, voici quelques exemples de données de la table d'erreur:

ErrorCode  Descript 
001   Problem with person file 
002   Problem with address file 
003   Problem with grade 

Voici les colonnes résultant de mon SELECT sur la personne avec un JOIN sur erreur:

Person.RecID Person.ErrorCode Error.Descript 
12345   001    Problem with person file 
12346   003    Problem with grade 
12347   002,003 

Ce que je suis en train pour obtenir est ce:

Person.RecID Person.ErrorCode Error.Descript 
12345   001    Problem with person file 
12346   003    Problem with grade 
12347   002,003   Problem with address file, Problem with grade 

Suggestions appréciées!

Répondre

-2

En regroupant les erreurs ensemble et les concaténer est une option:

SELECT *, GROUP_CONCAT(Person.ErrorCode) FROM Person 
GROUP BY Person.RecID 
+1

SQL Server ne possède pas GROUP_CONCAT. –

+1

GROUP_CONCAT n'est pas un mot-clé dans SQL Server. Vous pensez probablement à MySQL (?) –

1

dénormaliser person.errorcode avant la jonction avec error.errorcode

Je ne veux pas dénormaliser au niveau de la table, je signifie avec un code view ou sql.

+1

OP a dit dans la question 'Je sais que c'est un problème de schéma mais c'est une application vendeur et je n'ai aucun contrôle sur le schéma, donc je dois travailler avec ce que j'ai. ' –

5

Vous devriez voir: "Arrays and Lists in SQL Server 2005 and Beyond, When Table Value Parameters Do Not Cut it" by Erland Sommarskog, puis il existe plusieurs façons de fractionner une chaîne dans SQL Server. Cet article couvre les PROs et CONs de presque toutes les méthodes. en général, vous devez créer une fonction de séparation. Voici comment une fonction split peut être utilisé pour joindre les rangs:

SELECT 
    * 
    FROM dbo.yourSplitFunction(@Parameter) b 
     INNER JOIN YourCodesTable   c ON b.ListValue=c.CodeValue 

I prefer the number table approach to split a string in TSQL mais il existe de nombreuses façons de diviser les chaînes dans SQL Server, voir le lien précédent, ce qui explique les avantages et les inconvénients de chacun.

Pour la méthode de table de nombres au travail, vous devez faire une configuration de table de temps, ce qui va créer une table Numbers qui contient des lignes de 1 à 10 000:

SELECT TOP 10000 IDENTITY(int,1,1) AS Number 
    INTO Numbers 
    FROM sys.objects s1 
    CROSS JOIN sys.objects s2 
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number) 

Une fois la table des numéros est mis en place , créez cette fonction split:

CREATE FUNCTION [dbo].[FN_ListToTable] 
(
    @SplitOn char(1)  --REQUIRED, the character to split the @List string on 
    ,@List  varchar(8000)--REQUIRED, the list to split apart 
) 
RETURNS TABLE 
AS 
RETURN 
(

    ---------------- 
    --SINGLE QUERY-- --this will not return empty rows 
    ---------------- 
    SELECT 
     ListValue 
     FROM (SELECT 
        LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(@SplitOn, List2, number+1)-number - 1))) AS ListValue 
        FROM (
          SELECT @SplitOn + @List + @SplitOn AS List2 
         ) AS dt 
         INNER JOIN Numbers n ON n.Number < LEN(dt.List2) 
        WHERE SUBSTRING(List2, number, 1) = @SplitOn 
      ) dt2 
     WHERE ListValue IS NOT NULL AND ListValue!='' 

); 
GO 

Vous pouvez maintenant facilement découper une chaîne CSV dans une table et se joindre à elle:

DECLARE @ErrorCode table (ErrorCode varchar(20), Description varchar(30)) 
INSERT @ErrorCode VALUES ('001','Problem with person file') 
INSERT @ErrorCode VALUES ('002','Problem with address file') 
INSERT @ErrorCode VALUES ('003','Problem with grade') 

DECLARE @Person table (RecID int, ErrorCode varchar(20)) 
INSERT @Person VALUES (12345 ,'001' ) 
INSERT @Person VALUES (12346 ,'003' ) 
INSERT @Person VALUES (12347 ,'002,003') 


SELECT 
    p.RecID,c.ListValue,e.Description 
    FROM @Person          p 
     CROSS APPLY dbo.FN_ListToTable(',',p.ErrorCode) c 
     INNER JOIN @ErrorCode       e ON c.ListValue=e.ErrorCode 

SORTIE:

RecID  ListValue  Description    
----------- ------------- ------------------------- 
12345  001   Problem with person file 
12346  003   Problem with grade  
12347  002   Problem with address file 
12347  003   Problem with grade  

(4 row(s) affected) 

vous pouvez utiliser l'astuce XML pour concaténer les lignes de retour ensemble:

SELECT 
    t1.RecID,t1.ErrorCode 
     ,STUFF(
        (SELECT 
         ', ' + e.Description 
         FROM @Person          p 
          CROSS APPLY dbo.FN_ListToTable(',',p.ErrorCode) c 
          INNER JOIN @ErrorCode       e ON c.ListValue=e.ErrorCode 
         WHERE t1.RecID=p.RecID 
         ORDER BY p.ErrorCode 
         FOR XML PATH(''), TYPE 
        ).value('.','varchar(max)') 
        ,1,2, '' 
      ) AS ChildValues 
    FROM @Person t1 
    GROUP BY t1.RecID,t1.ErrorCode 

SORTIE:

RecID  ErrorCode   ChildValues 
----------- -------------------- ----------------------------------------------- 
12345  001     Problem with person file 
12346  003     Problem with grade 
12347  002,003    Problem with address file, Problem with grade 

(3 row(s) affected) 

Cela renvoie le même résultat défini comme ci-dessus, mais peut fonctionner mieux:

SELECT 
    t1.RecID,t1.ErrorCode 
     ,STUFF(
        (SELECT 
         ', ' + e.Description 
         FROM (SELECT ListValue FROM dbo.FN_ListToTable(',',t1.ErrorCode)) c 
          INNER JOIN @ErrorCode e ON c.ListValue=e.ErrorCode 
         ORDER BY c.ListValue 
         FOR XML PATH(''), TYPE 
        ).value('.','varchar(max)') 
        ,1,2, '' 
      ) AS ChildValues 
    FROM @Person t1 
    GROUP BY t1.RecID,t1.ErrorCode 
+1

+1 Très complet. –

+0

Approche très méthodique. +1 –

0

Vous pouvez utiliser une expression de table commune de prétendre que la table personne est normale:

;WITH PersonPrime as (
    SELECT RecID,ErrorCode,CAST(null as varchar(100)) as Remain from Person where Value not like '%,%' 
    UNION ALL 
    SELECT RecID,SUBSTRING(ErrorCode,1,CHARINDEX(',',ErrorCode)-1),SUBSTRING(ErrorCode,CHARINDEX(',',ErrorCode)+1,100) from Person where Value like '%,%' 
    UNION ALL 
    SELECT RecID,Remain,null FROM PersonPrime where Remain not like '%,%' 
    UNION ALL 
    SELECT RecID,SUBSTRING(Remain,1,CHARINDEX(',',Remain)-1),SUBSTRING(Remain,CHARINDEX(',',Remain)+1,100) from PersonPrime where Remain like '%,%' 
) 
SELECT RecID,ErrorCode from PersonPrime 

Et maintenant utiliser PersonPrime où vous auriez utilisé personne dans votre requête d'origine. Vous aurez besoin de CAST null à une colonne varchar aussi large que ErrorCode dans la table Person

0

La concaténation des descriptions d'erreurs peut ne pas être la bonne façon de procéder. Il ajoute une complexité inutile à l'instruction SQL qui sera extrêmement problématique à déboguer. Tous les ajouts ou changements futurs au SQL seront également difficiles. Votre meilleur pari est de générer un jeu de résultats qui est normalisé, même si votre schéma ne l'est pas.

SELECT Person.RecID, Person.ErrorCode, Error.ErrorCode, Error.Descript 
    FROM Person INNER JOIN Error 
    ON REPLACE(Person.ErrorCode, ' ', '') LIKE '%,' + CONVERT(VARCHAR,Error.ErrorCode) + ',%' 

Si une personne a plusieurs codes d'erreur défini, alors ce sera de retour une ligne pour chaque erreur spécifiées (abstraction faite des doublons). En utilisant votre exemple, cela retournera ceci.

Person.RecID Person.ErrorCode Error.ErrorCode Error.Descript 
12345   001    001    Problem with person file 
12346   003    003    Problem with grade 
12347   002,003   002    Problem with address file 
12347   002,003   003    Problem with grade 
Questions connexes