Considérons une table de base de données contenant des noms, avec trois lignes:
Peter
Paul
Mary
Existe-t-il un moyen facile de transformer cela en une seule chaîne de Peter, Paul, Mary
?
Considérons une table de base de données contenant des noms, avec trois lignes:
Peter
Paul
Mary
Existe-t-il un moyen facile de transformer cela en une seule chaîne de Peter, Paul, Mary
?
Réponses:
Si vous êtes sur SQL Server 2017 ou Azure, consultez la réponse de Mathieu Renda .
J'ai rencontré un problème similaire lorsque j'essayais de joindre deux tables avec des relations un à plusieurs. Dans SQL 2005, j'ai trouvé que cette XML PATH
méthode peut gérer la concaténation des lignes très facilement.
S'il y a une table appelée STUDENTS
SubjectID StudentName
---------- -------------
1 Mary
1 John
1 Sam
2 Alaina
2 Edward
Le résultat que j'attendais était:
SubjectID StudentName
---------- -------------
1 Mary, John, Sam
2 Alaina, Edward
J'ai utilisé ce qui suit T-SQL
:
SELECT Main.SubjectID,
LEFT(Main.Students,Len(Main.Students)-1) As "Students"
FROM
(
SELECT DISTINCT ST2.SubjectID,
(
SELECT ST1.StudentName + ',' AS [text()]
FROM dbo.Students ST1
WHERE ST1.SubjectID = ST2.SubjectID
ORDER BY ST1.SubjectID
FOR XML PATH ('')
) [Students]
FROM dbo.Students ST2
) [Main]
Vous pouvez faire la même chose de manière plus compacte si vous pouvez concaténer les virgules au début et utiliser substring
pour ignorer la première afin que vous n'ayez pas besoin de faire une sous-requête:
SELECT DISTINCT ST2.SubjectID,
SUBSTRING(
(
SELECT ','+ST1.StudentName AS [text()]
FROM dbo.Students ST1
WHERE ST1.SubjectID = ST2.SubjectID
ORDER BY ST1.SubjectID
FOR XML PATH ('')
), 2, 1000) [Students]
FROM dbo.Students ST2
<
ou &
. Voir le commentaire de @ BenHinman.
FOR XML PATH ('')
. Cela signifie qu'il ne doit pas être considéré comme fiable, car tout correctif ou mise à jour pourrait altérer son fonctionnement. Cela repose essentiellement sur une fonctionnalité obsolète.
FOR XML
est de générer du XML, pas de concaténer des chaînes arbitraires. Voilà pourquoi il échappe &
, <
et >
aux codes d'entité XML ( &
, <
, >
). Je suppose que ce sera aussi échapper "
et '
à "
et '
dans les attributs aussi bien. Il est pas GROUP_CONCAT()
, string_agg()
, array_agg()
, listagg()
, etc. , même si vous pouvez faire ce genre de le faire. Nous devrions passer notre temps à demander à Microsoft de mettre en place une fonction appropriée.
string_agg
dans v.Next. et tout cela peut disparaître.
Cette réponse peut renvoyer des résultats inattendus. Pour des résultats cohérents, utilisez l'une des méthodes FOR XML PATH détaillées dans d'autres réponses.
Utilisation COALESCE
:
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(@Names + ', ', '') + Name
FROM People
Juste quelques explications (puisque cette réponse semble obtenir des vues relativement régulières):
1) Pas besoin d'initialiser @Names
avec une valeur de chaîne vide.
2) Pas besoin de retirer un séparateur supplémentaire à la fin.
@Names
NULL après cette ligne, et la ligne suivante recommencera comme une chaîne vide. Facilement corrigé avec l'un des deux solutions:DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(@Names + ', ', '') + Name
FROM People
WHERE Name IS NOT NULL
ou:
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(@Names + ', ', '') +
ISNULL(Name, 'N/A')
FROM People
Selon le comportement que vous souhaitez (la première option filtre uniquement les valeurs NULL , la seconde les conserve dans la liste avec un message marqueur [remplacez «N / A» par ce qui vous convient]).
À partir de la prochaine version de SQL Server, nous pouvons enfin concaténer sur plusieurs lignes sans avoir à recourir à une variable ou à une sorcellerie XML.
Sans regroupement
SELECT STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department;
Avec regroupement:
SELECT GroupName, STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department
GROUP BY GroupName;
Avec regroupement et sous-tri
SELECT GroupName, STRING_AGG(Name, ', ') WITHIN GROUP (ORDER BY Name ASC) AS Departments
FROM HumanResources.Department
GROUP BY GroupName;
Une méthode non encore montrée via la XML
data()
commande dans MS SQL Server est:
Supposons une table appelée NameList avec une colonne appelée FName,
SELECT FName + ', ' AS 'data()'
FROM NameList
FOR XML PATH('')
Retour:
"Peter, Paul, Mary, "
Seule la virgule supplémentaire doit être traitée.
Modifier: Comme adopté à partir du commentaire de @ NReilingh, vous pouvez utiliser la méthode suivante pour supprimer la virgule de fin. En supposant les mêmes noms de table et de colonne:
STUFF(REPLACE((SELECT '#!' + LTRIM(RTRIM(FName)) AS 'data()' FROM NameList
FOR XML PATH('')),' #!',', '), 1, 2, '') as Brands
+ ', '
il ajoute toujours un espace unique entre chaque élément concaténé.
SELECT STUFF(REPLACE((SELECT '#!'+city AS 'data()' FROM #cityzip FOR XML PATH ('')),' #!',', '),1,2,'')
SELECT Stuff(
(SELECT N', ' + Name FROM Names FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),1,2,N'')
vous pouvez utiliser la syntaxe FOR JSON
c'est à dire
SELECT per.ID,
Emails = JSON_VALUE(
REPLACE(
(SELECT _ = em.Email FROM Email em WHERE em.Person = per.ID FOR JSON PATH)
,'"},{"_":"',', '),'$[0]._'
)
FROM Person per
Et le résultat deviendra
Id Emails
1 abc@gmail.com
2 NULL
3 def@gmail.com, xyz@gmail.com
Cela fonctionnera même si vos données contiennent des caractères XML non valides
le '"},{"_":"'
est sûr parce que si vos données contiennent, '"},{"_":"',
il sera échappé à"},{\"_\":\"
Vous pouvez remplacer ', '
par n'importe quel séparateur de chaînes
Vous pouvez utiliser la nouvelle fonction STRING_AGG
<
, >
, &
, etc. , qui FOR XML PATH('')
échapperont automatiquement.
Dans MySQL, il existe une fonction, GROUP_CONCAT () , qui vous permet de concaténer les valeurs de plusieurs lignes. Exemple:
SELECT 1 AS a, GROUP_CONCAT(name ORDER BY name ASC SEPARATOR ', ') AS people
FROM users
WHERE id IN (1,2,3)
GROUP BY a
SEPARATOR '", "'
je manquerai quelques caractères à la fin de la dernière entrée. pourquoi cela peut-il arriver?
CHAR
, vous devez le caster, par exemple via GROUP_CONCAT( CAST(id AS CHAR(8)) ORDER BY id ASC SEPARATOR ',')
2) si vous avez de nombreuses valeurs à venir, vous devez augmenter le group_concat_max_len
comme écrit dans stackoverflow.com/a/1278210/1498405
Utilisez COALESCE - En savoir plus d'ici
À titre d'exemple:
102
103
104
Ensuite, écrivez le code ci-dessous dans le serveur SQL,
Declare @Numbers AS Nvarchar(MAX) -- It must not be MAX if you have few numbers
SELECT @Numbers = COALESCE(@Numbers + ',', '') + Number
FROM TableName where Number IS NOT NULL
SELECT @Numbers
La sortie serait:
102,103,104
Declare @Numbers AS Nvarchar(MAX)
et cela a bien fonctionné. Pouvez-vous expliquer pourquoi vous recommandez de ne pas l'utiliser s'il vous plaît?
Les tableaux Postgres sont géniaux. Exemple:
Créez des données de test:
postgres=# \c test
You are now connected to database "test" as user "hgimenez".
test=# create table names (name text);
CREATE TABLE
test=# insert into names (name) values ('Peter'), ('Paul'), ('Mary');
INSERT 0 3
test=# select * from names;
name
-------
Peter
Paul
Mary
(3 rows)
Les agréger dans un tableau:
test=# select array_agg(name) from names;
array_agg
-------------------
{Peter,Paul,Mary}
(1 row)
Convertissez le tableau en une chaîne séparée par des virgules:
test=# select array_to_string(array_agg(name), ', ') from names;
array_to_string
-------------------
Peter, Paul, Mary
(1 row)
TERMINÉ
Depuis PostgreSQL 9.0, c'est encore plus simple .
select array_to_string(array_agg(name||'('||id||')'
Oracle 11g Release 2 prend en charge la fonction LISTAGG. Documentation ici .
COLUMN employees FORMAT A50
SELECT deptno, LISTAGG(ename, ',') WITHIN GROUP (ORDER BY ename) AS employees
FROM emp
GROUP BY deptno;
DEPTNO EMPLOYEES
---------- --------------------------------------------------
10 CLARK,KING,MILLER
20 ADAMS,FORD,JONES,SCOTT,SMITH
30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD
3 rows selected.
Soyez prudent lors de l'implémentation de cette fonction s'il est possible que la chaîne résultante dépasse 4 000 caractères. Cela lèvera une exception. Si tel est le cas, vous devez gérer l'exception ou lancer votre propre fonction qui empêche la chaîne jointe de dépasser 4 000 caractères.
LISTAGG
fonctionne parfaitement! Lisez simplement le document lié ici. wm_concat
supprimé à partir de la version 12c.
Dans SQL Server 2005 et versions ultérieures, utilisez la requête ci-dessous pour concaténer les lignes.
DECLARE @t table
(
Id int,
Name varchar(10)
)
INSERT INTO @t
SELECT 1,'a' UNION ALL
SELECT 1,'b' UNION ALL
SELECT 2,'c' UNION ALL
SELECT 2,'d'
SELECT ID,
stuff(
(
SELECT ','+ [Name] FROM @t WHERE Id = t.Id FOR XML PATH('')
),1,1,'')
FROM (SELECT DISTINCT ID FROM @t ) t
<
ou &
.
Je n'ai pas accès à un serveur SQL à la maison, donc je suppose que la syntaxe ici, mais c'est plus ou moins:
DECLARE @names VARCHAR(500)
SELECT @names = @names + ' ' + Name
FROM Names
SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ' ' END + Name FROM Names
SELECT @names = @names + ISNULL(' ' + Name, '')
Une solution CTE récursive a été suggérée, mais aucun code n'a été fourni. Le code ci-dessous est un exemple de CTE récursif. Notez que bien que les résultats correspondent à la question, les données ne correspondent pas tout à fait à la description donnée, car je suppose que vous voulez vraiment faire cela sur des groupes de lignes, pas toutes les lignes du tableau. Le modifier pour qu'il corresponde à toutes les lignes du tableau reste un exercice pour le lecteur.
;WITH basetable AS (
SELECT
id,
CAST(name AS VARCHAR(MAX)) name,
ROW_NUMBER() OVER (Partition BY id ORDER BY seq) rw,
COUNT(*) OVER (Partition BY id) recs
FROM (VALUES
(1, 'Johnny', 1),
(1, 'M', 2),
(2, 'Bill', 1),
(2, 'S.', 4),
(2, 'Preston', 5),
(2, 'Esq.', 6),
(3, 'Ted', 1),
(3, 'Theodore', 2),
(3, 'Logan', 3),
(4, 'Peter', 1),
(4, 'Paul', 2),
(4, 'Mary', 3)
) g (id, name, seq)
),
rCTE AS (
SELECT recs, id, name, rw
FROM basetable
WHERE rw = 1
UNION ALL
SELECT b.recs, r.ID, r.name +', '+ b.name name, r.rw + 1
FROM basetable b
INNER JOIN rCTE r ON b.id = r.id AND b.rw = r.rw + 1
)
SELECT name
FROM rCTE
WHERE recs = rw AND ID=4
name
colonne en une chaîne séparée par des virgules pour 4 groupes de id
s. À première vue, je pense que c'est plus de travail que ce que font la plupart des autres solutions pour SQL Server.
À partir de PostgreSQL 9.0, c'est assez simple:
select string_agg(name, ',')
from names;
Dans les versions antérieures à 9.0 array_agg()
peuvent être utilisées comme indiqué par hgmnz
SELECT string_agg(non_text_type::text, ',') FROM table
varchar
ouchar
Dans SQL Server vNext, cela sera intégré à la fonction STRING_AGG, en savoir plus à ce sujet ici: https://msdn.microsoft.com/en-us/library/mt790580.aspx
L'utilisation de XML m'a aidé à séparer les lignes par des virgules. Pour la virgule supplémentaire, nous pouvons utiliser la fonction de remplacement de SQL Server. Au lieu d'ajouter une virgule, l'utilisation de l'AS 'data ()' concaténera les lignes avec des espaces, qui pourront ensuite être remplacés par des virgules comme syntaxe écrite ci-dessous.
REPLACE(
(select FName AS 'data()' from NameList for xml path(''))
, ' ', ', ')
Une solution prête à l'emploi, sans virgule supplémentaire:
select substring(
(select ', '+Name AS 'data()' from Names for xml path(''))
,3, 255) as "MyList"
Une liste vide entraînera une valeur NULL. Habituellement, vous insérerez la liste dans une colonne de tableau ou une variable de programme: ajustez la longueur maximale de 255 selon vos besoins.
(Diwakar et Jens Frandsen ont fourni de bonnes réponses, mais doivent être améliorées.)
', '
par ','
si vous ne voulez pas d'espace supplémentaire.
SELECT STUFF((SELECT ', ' + name FROM [table] FOR XML PATH('')), 1, 2, '')
Voici un exemple:
DECLARE @t TABLE (name VARCHAR(10))
INSERT INTO @t VALUES ('Peter'), ('Paul'), ('Mary')
SELECT STUFF((SELECT ', ' + name FROM @t FOR XML PATH('')), 1, 2, '')
--Peter, Paul, Mary
DECLARE @Names VARCHAR(8000)
SELECT @name = ''
SELECT @Names = @Names + ',' + Names FROM People
SELECT SUBSTRING(2, @Names, 7998)
Cela place la virgule errante au début.
Cependant, si vous avez besoin d'autres colonnes, ou pour CSV une table enfant, vous devez l'encapsuler dans un champ défini par l'utilisateur scalaire (UDF).
Vous pouvez également utiliser le chemin XML comme sous-requête corrélée dans la clause SELECT (mais je devrais attendre de retourner au travail parce que Google ne fait pas de travail à la maison :-)
Avec les autres réponses, la personne qui lit la réponse doit connaître une table de domaine spécifique telle que véhicule ou étudiant. La table doit être créée et remplie de données pour tester une solution.
Voici un exemple qui utilise la table SQL Server "Information_Schema.Columns". En utilisant cette solution, aucune table ne doit être créée ni aucune donnée ajoutée. Cet exemple crée une liste de noms de colonnes séparés par des virgules pour toutes les tables de la base de données.
SELECT
Table_Name
,STUFF((
SELECT ',' + Column_Name
FROM INFORMATION_SCHEMA.Columns Columns
WHERE Tables.Table_Name = Columns.Table_Name
ORDER BY Column_Name
FOR XML PATH ('')), 1, 1, ''
)Columns
FROM INFORMATION_SCHEMA.Columns Tables
GROUP BY TABLE_NAME
Pour les bases de données Oracle, consultez cette question: comment concaténer plusieurs lignes en une seule dans Oracle sans créer de procédure stockée?
La meilleure réponse semble être de @Emmanuel, en utilisant la fonction intégrée LISTAGG (), disponible dans Oracle 11g version 2 et versions ultérieures.
SELECT question_id,
LISTAGG(element_id, ',') WITHIN GROUP (ORDER BY element_id)
FROM YOUR_TABLE;
GROUP BY question_id
comme l'a souligné @ user762952, et selon la documentation d'Oracle http://www.oracle-base.com/articles/misc/string-aggregation-techniques.php , la fonction WM_CONCAT () est également une option. Il semble stable, mais Oracle recommande explicitement de ne pas l'utiliser pour toute application SQL, donc utilisez-le à vos risques et périls.
En dehors de cela, vous devrez écrire votre propre fonction; le document Oracle ci-dessus contient un guide sur la façon de procéder.
Pour éviter les valeurs nulles, vous pouvez utiliser CONCAT ()
DECLARE @names VARCHAR(500)
SELECT @names = CONCAT(@names, ' ', name)
FROM Names
select @names
J'ai vraiment aimé l'élégance de la réponse de Dana . Je voulais juste le terminer.
DECLARE @names VARCHAR(MAX)
SET @names = ''
SELECT @names = @names + ', ' + Name FROM Names
-- Deleting last two symbols (', ')
SET @sSql = LEFT(@sSql, LEN(@sSql) - 1)
SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ', ' END + Name FROM Names
alors vous n'aurez pas à la tronquer par la suite.
Cette réponse nécessitera certains privilèges sur le serveur pour fonctionner.
Les assemblages sont une bonne option pour vous. Il existe de nombreux sites qui expliquent comment le créer. Celui que je pense est très bien expliqué est celui- ci
Si vous le souhaitez, j'ai déjà créé l'assemblage, et il est possible de télécharger la DLL ici .
Une fois que vous l'avez téléchargé, vous devrez exécuter le script suivant dans votre serveur SQL:
CREATE Assembly concat_assembly
AUTHORIZATION dbo
FROM '<PATH TO Concat.dll IN SERVER>'
WITH PERMISSION_SET = SAFE;
GO
CREATE AGGREGATE dbo.concat (
@Value NVARCHAR(MAX)
, @Delimiter NVARCHAR(4000)
) RETURNS NVARCHAR(MAX)
EXTERNAL Name concat_assembly.[Concat.Concat];
GO
sp_configure 'clr enabled', 1;
RECONFIGURE
Notez que le chemin d'accès à l'assembly peut être accessible au serveur. Puisque vous avez réussi toutes les étapes, vous pouvez utiliser la fonction comme:
SELECT dbo.Concat(field1, ',')
FROM Table1
J'espère que cela aide!!!
Exemple complet MySQL:
Nous avons des utilisateurs qui peuvent avoir de nombreuses données et nous voulons avoir une sortie, où nous pouvons voir toutes les données des utilisateurs dans une liste:
Résultat:
___________________________
| id | rowList |
|-------------------------|
| 0 | 6, 9 |
| 1 | 1,2,3,4,5,7,8,1 |
|_________________________|
Configuration de la table:
CREATE TABLE `Data` (
`id` int(11) NOT NULL,
`user_id` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=latin1;
INSERT INTO `Data` (`id`, `user_id`) VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 1),
(5, 1),
(6, 0),
(7, 1),
(8, 1),
(9, 0),
(10, 1);
CREATE TABLE `User` (
`id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `User` (`id`) VALUES
(0),
(1);
Requete:
SELECT User.id, GROUP_CONCAT(Data.id ORDER BY Data.id) AS rowList FROM User LEFT JOIN Data ON User.id = Data.user_id GROUP BY User.id
J'utilise généralement select comme ceci pour concaténer des chaînes dans SQL Server:
with lines as
(
select
row_number() over(order by id) id, -- id is a line id
line -- line of text.
from
source -- line source
),
result_lines as
(
select
id,
cast(line as nvarchar(max)) line
from
lines
where
id = 1
union all
select
l.id,
cast(r.line + N', ' + l.line as nvarchar(max))
from
lines l
inner join
result_lines r
on
l.id = r.id + 1
)
select top 1
line
from
result_lines
order by
id desc
Cela a fonctionné pour moi ( SqlServer 2016 ):
SELECT CarNamesString = STUFF((
SELECT ',' + [Name]
FROM tbl_cars
FOR XML PATH('')
), 1, 1, '')
Voici la source: https://www.mytecbits.com/
Et une solution pour MySql (puisque cette page apparaît dans Google pour MySql)
SELECT [Name],
GROUP_CONCAT(DISTINCT [Name] SEPARATOR ',')
FROM tbl_cars
Dans Oracle, c'est le cas wm_concat
. Je crois que cette fonction est disponible dans la version 10g et supérieure.
Cela peut aussi être utile
create table #test (id int,name varchar(10))
--use separate inserts on older versions of SQL Server
insert into #test values (1,'Peter'), (1,'Paul'), (1,'Mary'), (2,'Alex'), (3,'Jack')
DECLARE @t VARCHAR(255)
SELECT @t = ISNULL(@t + ',' + name, name) FROM #test WHERE id = 1
select @t
drop table #test
Retour
Peter,Paul,Mary