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!
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
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>','')
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 ;)
SqlServer 2017 a maintenant STRING_AGG qui regroupe plusieurs chaînes en une seule à l'aide d'un séparateur donné.
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)
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,
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;
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
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
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
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
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)?
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
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.
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