web-dev-qa-db-fra.com

Concaténer les valeurs de ligne T-SQL

J'essaie de rassembler des données pour un rapport et je dois concaténer les valeurs de ligne de l'une des tables. Voici la structure de base de la table:

Avis 

 ReviewID  
 ReviewDate  

Réviseurs 

 ReviewerID  
 ReviewID  
 UserID  

Utilisateurs 

UserID  
FName  
LName  

C'est une relation M: M. Chaque examen peut avoir de nombreux examinateurs; chaque utilisateur peut être associé à de nombreuses critiques.

Fondamentalement, tout ce que je veux voir, c'est Reviews.ReviewID, Reviews.ReviewDate et une chaîne concaténée du nom FName de tous les utilisateurs associés à cet examen (séparés par des virgules).

Au lieu de:

ReviewID---ReviewDate---User  
1----------12/1/2009----Bob  
1----------12/1/2009----Joe  
1----------12/1/2009----Frank  
2----------12/9/2009----Sue  
2----------12/9/2009----Alice  

Afficher ceci:

ReviewID---ReviewDate----Users  
1----------12/1/2009-----Bob, Joe, Frank  
2----------12/9/2009-----Sue, Alice

J'ai trouvé this article décrivant certaines manières de le faire, mais la plupart d'entre elles ne semblent traiter que d'une table, pas de plusieurs; Malheureusement, mon SQL-fu n'est pas assez fort pour les adapter à ma situation. Je suis particulièrement intéressé par l'exemple de ce site qui utilise FOR XML PATH (), qui semble le plus propre et le plus direct.

SELECT p1.CategoryId,
( SELECT ProductName + ', '
  FROM Northwind.dbo.Products p2
  WHERE p2.CategoryId = p1.CategoryId
  ORDER BY ProductName FOR XML PATH('')
) AS Products
FROM Northwind.dbo.Products p1
GROUP BY CategoryId;

Quelqu'un peut-il me donner un coup de main avec ça? Toute aide serait grandement appréciée!

32
Sesame

Regarde ça

DECLARE @Reviews TABLE(
        ReviewID INT,
        ReviewDate DATETIME
)

DECLARE @Reviewers TABLE(
        ReviewerID   INT,
        ReviewID   INT,
        UserID INT
)

DECLARE @Users TABLE(
        UserID  INT,
        FName  VARCHAR(50),
        LName VARCHAR(50)
)

INSERT INTO @Reviews SELECT 1, '12 Jan 2009'
INSERT INTO @Reviews SELECT 2, '25 Jan 2009'

INSERT INTO @Users SELECT 1, 'Bob', ''
INSERT INTO @Users SELECT 2, 'Joe', ''
INSERT INTO @Users SELECT 3, 'Frank', ''
INSERT INTO @Users SELECT 4, 'Sue', ''
INSERT INTO @Users SELECT 5, 'Alice', ''

INSERT INTO @Reviewers SELECT 1, 1, 1
INSERT INTO @Reviewers SELECT 2, 1, 2
INSERT INTO @Reviewers SELECT 3, 1, 3
INSERT INTO @Reviewers SELECT 4, 2, 4
INSERT INTO @Reviewers SELECT 5, 2, 5

SELECT  *,
        ( 
            SELECT  u.FName + ','
            FROM    @Users u INNER JOIN 
                    @Reviewers rs ON u.UserID = rs.UserID
            WHERE   rs.ReviewID = r.ReviewID
            FOR XML PATH('')
        ) AS Products
FROM    @Reviews r
33
Adriaan Stander

Il s’avère qu’il existe un moyen encore plus simple de le faire, qui ne nécessite pas de fichier UDF:

select replace(replace(replace((cast((
        select distinct columnName as X
        from tableName 
        for xml path('')) as varchar(max))), 
   '</X><X>', ', '),'<X>', ''),'</X>','')
20
Digital Mindspring

Avait un problème similaire et a trouvé une solution douce après avoir joué avec le code pendant 15 minutes

declare @result varchar(1000)
select @result = COALESCE(@result+','+A.col1, A.col1)
                FROM (  select  col1
                        from [table] 
                ) A
select @result

Renvoie le résultat sous la forme valeur1, valeur2, valeur3, valeur4

Prendre plaisir ;)

10
Talha

SqlServer 2017 a maintenant STRING_AGG qui regroupe plusieurs chaînes en une seule à l'aide d'un séparateur donné.

7
John

Comme vous l'avez décrit, il existe trois manières de traiter les données de cumul: 1. Utilisez un curseur, 2. Utilisez un fichier UDF ou 3. Utilisez un a agrégat personnalisé (écrit en .NET CLR).
Le curseur et la FDU sont plutôt lents. (environ 0,1 seconde par rangée). L'agrégat personnalisé CLR est étonnamment rapide. (environ 0,001 seconde par rangée)

Microsoft expédie le code (pour faire exactement ce que vous voulez) dans le cadre du SDK pour SQL 2005. Si vous l'avez installé, vous devriez pouvoir trouver le code dans ce dossier: C:\Program Files\Microsoft SQL Serveur\90\Samples\Engine\Programmabilité\CLR\StringUtilities . Vous voudrez peut-être aussi cet article dans MSDN. Il parle de l'installation de l'agrégat personnalisé et de son activation: http://msdn.Microsoft.com/en-us/library/ms161551(SQL.90).aspx

Une fois que vous avez compilé et installé l'agrégat personnalisé, vous devriez pouvoir interroger comme ceci:

SELECT Reviews.ReviewID, ReviewDate, dbo.StringUtilities.Concat(FName) AS [User]
FROM Reviews INNER JOIN Reviewers ON Reviews.ReviewID = Reviewers.ReviewID
   INNER JOIN Users ON Reviews.UserID = Users.UserID
GROUP BY ReviewID, ReviewDate;

et obtenez un résultat comme vous l'avez montré (ci-dessus)

6
TimG
select p1.Availability ,COUNT(*),
(select  name+','  from AdventureWorks2008.Production.Location p2 where 
p1.Availability=p2.Availability for XML path(''),type).value('.','varchar(max)') 
as Name  from AdventureWorks2008.Production.Location p1 group by Availability

Résultat

Availability  COUNT     Name  
---------------------------------------------------------------------------------
0.00    7   Tool Crib,Sheet Metal Racks,Paint Shop,Paint Storage,Metal 
                    Storage,Miscellaneous Storage,Finished Goods Storage,
80.00   1   Specialized Paint,
96.00   1   Frame Forming,
108.00  1   Frame Welding,
120.00  4   Debur and Polish,Paint,Subassembly,Final Assembly,
5
pradeep
Select R.ReviewID, ReviewDate
, (Select  FName + ', ' 
   from Users 
   where UserID = R.UserID 
   order by FName FOR XML PATH(')
) as [Users]
from Reviews
inner join Reviewers AS R
  On Reviews.ReviewID = R.ReviewID
Group By R.ReviewID, ReviewDate;
3
JeffO

Un UDF serait un bon moyen de résoudre ce problème.

Définissez simplement une fonction T-SQL (UDF) qui prend un param int (ID produit) et renvoie une chaîne (concaténation des noms associés au produit.) Si le nom de votre méthode est GetProductNames, votre requête pourrait ressembler à ceci:

SELECT p1.CategoryId, dbo.GetProductNames(p1.CategoryId)
FROM Northwind.dbo.Products p1
GROUP BY CategoryId
3
Paul Sasik

Désormais, SQL Server 2017 propose une nouvelle fonction T-SQL appelée STRING_AGG :
C’est une nouvelle fonction d’agrégation qui concatène les valeurs des expressions de chaîne et place des valeurs de séparateur entre elles.
Le séparateur n'est pas ajouté à la fin de la chaîne.

Exemple: 

SELECT STRING_AGG ( ISNULL(FirstName,'N/A'), ',') AS csv 
FROM Person.Person; 

le jeu de résultats:

John,N/A,Mike,Peter,N/A,N/A,Alice,Bob
3
Wael Ali

Essaye ça:

 Declare @Revs Table 
 (RevId int Priimary Key Not Null,
  RevDt DateTime Null,
  users varChar(1000) default '')

 Insert @Revs (RevId, RevDt)
 Select Distinct ReviewId, ReviewDate
 From Reviews
 Declare @UId Integer
 Set @Uid = 0
 While Exists (Select * From Users
               Where UserID > @Uid)
 Begin
    Update @Revs Set
      users = users + u.fName + ', '
    From @Revs R 
       Join Reviewers uR On ur.ReviewId = R.RId
       Join users u On u.UserId = uR.UserId 
    Where uR.UserId = @UId
    Select @Uid = Min(UserId)
    From users
    Where UserId > @UId
  End
  Select * From @Revs
3
Charles Bretana

Créez une table temporaire dans laquelle vider vos données. Utilisez ensuite la méthode FOR XML PATH. La requête externe est nécessaire pour supprimer la dernière virgule de la liste.

CREATE TABLE #ReviewInfo (
ReviewId INT,
ReviewDate DATETIME,
Reviewer VARCHAR(1000))

INSERT INTO #ReviewInfo (ReviewId, ReviewDate, Reviewer)
SELECT r.ReviewId, r.ReviewDate, u.FName
FROM Reviews r
JOIN Reviewers rs ON r.ReviewId = rs.ReviewId
JOIN Users u ON u.UserId = rs.UserId

SELECT ReviewId, ReviewDate, LEFT(Users, LEN(Users)-1)
FROM (
SELECT ReviewId, ReviewDate, 
(
    SELECT Reviewer + ', '
    FROM #ReviewInfo ri2
    WHERE ri2.ReviewId = ri1.ReviewId
    ORDER BY Reviewer
    FOR XML PATH('')
) AS Users
FROM #ReviewInfo ri1
GROUP BY ReviewId, ReviewDate
) a

DROP TABLE #ReviewInfo
2
Bradley

semble avoir besoin de la fonctionnalité de group_concat (de mysql). ceci a été traité ici pour un autre ensemble de données de test: Comment retourner plusieurs valeurs dans une colonne (T-SQL)?

2
Peter Carrero
select 
      p1.Availability,
      COUNT(*),
      (
          select  name+',' 
          from AdventureWorks2008.Production.Location p2 
          where p1.Availability=p2.Availability 
          for XML path(''),type
      ).value('.','varchar(max)') as Name  
 from AdventureWorks2008.Production.Location p1 
 group by Availability
2
prathmanu
STRING_AGG ( expression, separator ) [ <order_clause> ]

<order_clause> ::=   
    WITHIN GROUP ( ORDER BY <order_by_expression_list> [ ASC | DESC ] )

Je suis arrivé à Stackoverflow à la recherche de la fonction d'agrégat de chaîne de serveur SQL. 

La question pertinente avait été clôturée, marquée comme une copie de cette question et je suis donc obligé de répondre ici ou pas du tout.

Voir https://docs.Microsoft.com/en-us/sql/t-sql/functions/string-agg-transact-sql?view=sql-server-2017 pour plus de détails.

0
user3070485

Lorsque le nombre d'éléments est faible, vous pouvez utiliser ROW_NUMBER () OVER PARTITION BY:

declare @t table (col1 int, col2 varchar)
insert into @t VALUES (1,'A')
insert into @t VALUES (1,'B')
insert into @t VALUES (1,'C')
insert into @t VALUES (1,'D')
insert into @t VALUES (1,'E')
insert into @t VALUES (2,'X')
insert into @t VALUES (3,'Y')

select col1,
    MAX(CASE seq WHEN 1 THEN        col2 ELSE '' END ) + 
    MAX(CASE seq WHEN 2 THEN ', ' + col2 ELSE '' END ) + 
    MAX(CASE seq WHEN 3 THEN ', ' + col2 ELSE '' END ) +
    MAX(CASE seq WHEN 4 THEN ', ' + col2 ELSE '' END ) +
    MAX(CASE seq WHEN 5 THEN ',...' ELSE '' END ) 
    as col2
from (
    select col1, col2, ROW_NUMBER() OVER ( PARTITION BY col1 ORDER BY col2 ) seq
    from @t
    group by col1, col2
) x
group by col1
0
Igor Krupitsky