Comment concaténer du texte de plusieurs lignes en une seule chaîne de texte dans SQL Server?
-
10-07-2019 - |
Question
Considérons une table de base de données contenant des noms, avec trois lignes:
Peter
Paul
Mary
Existe-t-il un moyen simple de transformer cela en une seule chaîne de Peter, Paul, Mary
?
La solution
Si vous utilisez SQL Server 2017 ou Azure, consultez la réponse de Mathieu Renda .
J'avais un problème similaire lorsque j'essayais de joindre deux tables avec des relations un à plusieurs. Dans SQL 2005, la méthode XML PATH
permettait de gérer très facilement la concaténation des lignes.
S'il existe une table appelée ÉTUDIANTS
SubjectID StudentName
---------- -------------
1 Mary
1 John
1 Sam
2 Alaina
2 Edward
Le résultat attendu était:
SubjectID StudentName
---------- -------------
1 Mary, John, Sam
2 Alaina, Edward
J'ai utilisé le T-SQL
suivant:
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 d'une manière plus compacte si vous pouvez concattre les virgules au début et utiliser sous-chaîne
pour ignorer la première, de sorte que vous n'avez 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
Autres conseils
Cette réponse peut renvoyer 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.
Utiliser 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):
- La coalesce n’est en réalité qu’un tricheur utile qui accomplit deux choses:
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.
- La solution ci-dessus donnera des résultats incorrects si une ligne a une valeur de nom NULL (s'il existe un NULL , le NULL fera en sorte que
@Names
NULL après cette ligne, la ligne suivante recommencera sous la forme d'une chaîne vide. Facilement résolu par l'une des deux solutions suivantes:
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
En fonction du comportement souhaité (la première option filtre les NULL , la deuxième option les garde dans la liste avec un message de marqueur [remplacer "N / A" par ce qui convient à vous]).
Une méthode non encore affichée via la commande XML
data ()
dans MS SQL Server est la suivante:
Supposez la table appelée NameList avec une colonne appelée FName,
SELECT FName + ', ' AS 'data()'
FROM NameList
FOR XML PATH('')
renvoie:
"Peter, Paul, Mary, "
Seules les virgules supplémentaires doivent être traitées.
Modifier: Comme adopté dans le 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
SQL Server 2017+ et SQL Azure: STRING_AGG
À partir de la prochaine version de SQL Server, nous pouvons enfin concaténer des lignes sans avoir à recourir à une variable ou à une sorcellerie XML.
Sans regroupement
SELECT STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department;
Avec le 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;
Dans SQL Server 2005
SELECT Stuff(
(SELECT N', ' + Name FROM Names FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),1,2,N'')
Dans SQL Server 2016
vous pouvez utiliser le FOR syntaxe JSON
i.e.
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 sans danger, car si vos données contiennent "" ", {" _ ",": ","
il sera échappé à "}, {\" quot _. ": \"
Vous pouvez remplacer ','
par tout séparateur de chaîne
Et dans SQL Server 2017, base de données Azure SQL
Vous pouvez utiliser le nouveau STRING_AGG fonction
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
Utilisez COALESCE - En savoir plus d'ici
Par exemple:
102
103
104
Écrivez ensuite 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
Le résultat serait:
102,103,104
Les tableaux Postgres sont géniaux. Exemple:
Créer 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)
Agrégez-les dans un tableau:
test=# select array_agg(name) from names;
array_agg
-------------------
{Peter,Paul,Mary}
(1 row)
Convertissez le tableau en chaîne délimitée par des virgules:
test=# select array_to_string(array_agg(name), ', ') from names;
array_to_string
-------------------
Peter, Paul, Mary
(1 row)
FAIT
Depuis PostgreSQL 9.0, il est encore plus simple .
Oracle 11g version 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.
Avertissement
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. Il va lancer 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.
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
Je n'ai pas accès à un serveur SQL Server chez moi, alors je suppose que la syntaxe est la bonne, mais elle est plus ou moins:
DECLARE @names VARCHAR(500)
SELECT @names = @names + ' ' + Name
FROM Names
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 le faire. sur des groupes de lignes, pas toutes les lignes de la table. Le changer pour qu'il corresponde à toutes les lignes du tableau est laissé comme 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
Vous devez créer une variable qui contiendra votre résultat final et la sélectionnera comme cela.
La solution la plus simple
DECLARE @char VARCHAR(MAX);
SELECT @char = COALESCE(@char + ', ' + [column], [column])
FROM [table];
PRINT @char;
À partir de PostgreSQL 9.0, ceci est assez simple:
select string_agg(name, ',')
from names;
Dans les versions antérieures à 9.0, array_agg ()
peut être utilisé comme indiqué par hgmnz
Dans SQL Server vNext, cela sera intégré à la fonction STRING_AGG. Pour en savoir plus, cliquez ici: https://msdn.microsoft.com/en-us/library/mt790580.aspx
L’utilisation de XML m’a aidé à obtenir des lignes séparées 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 dans la 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 donnera une valeur NULL. En général, vous insérez la liste dans une colonne de table ou une variable de programme: ajustez la longueur maximale à votre besoin.
(Diwakar et Jens Frandsen ont donné de bonnes réponses, mais doivent être améliorés.)
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)
Ceci place la virgule parasite au début.
Cependant, si vous avez besoin d'autres colonnes, ou pour créer une table enfant au format CSV, vous devez envelopper cette zone dans un champ défini par l'utilisateur scalaire (UDF).
Vous pouvez également utiliser le chemin XML en tant que sous-requête corrélée dans la clause SELECT (mais je devrais attendre que je retourne au travail car Google ne fonctionne pas à la maison: -)
Avec les autres réponses, la personne qui lit la réponse doit connaître un tableau de domaine spécifique, tel que véhicule ou élève. La table doit être créée et renseignée avec des données pour tester une solution.
Vous trouverez ci-dessous un exemple d'utilisation de SQL Server " Information_Schema.Columns " table. En utilisant cette solution, aucune table ni addition de données ne doit être créé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 la question suivante: Comment concaténer plusieurs lignes en une seule dans Oracle sans créer de procédure stockée?
La meilleure réponse semble être celle de @Emmanuel, à l'aide de la fonction intégrée LISTAGG (), disponible dans Oracle 11g version 2 et ultérieure.
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. Cela semble stable, mais Oracle déconseille explicitement de l’utiliser pour toute application SQL, donc utilisez-le à vos risques et périls.
Autre que cela, vous devrez écrire votre propre fonction; Le document Oracle ci-dessus contient un guide expliquant comment procéder.
J'ai vraiment aimé l'élégance de La réponse de Dana . Je voulais juste le compléter.
DECLARE @names VARCHAR(MAX)
SET @names = ''
SELECT @names = @names + ', ' + Name FROM Names
-- Deleting last two symbols (', ')
SET @sSql = LEFT(@sSql, LEN(@sSql) - 1)
Pour éviter les valeurs NULL, vous pouvez utiliser CONCAT ()
DECLARE @names VARCHAR(500)
SELECT @names = CONCAT(@names, ' ', name)
FROM Names
select @names
Cette réponse nécessitera un certain privilège sur le serveur pour fonctionner.
Les assemblages sont une bonne option pour vous. Il y a beaucoup de sites qui expliquent comment le créer. Celui que je trouve très bien expliqué est le suivant: un
Si vous le souhaitez, j'ai déjà créé l'assembly et il est possible de télécharger la DLL ici .
Une fois que vous l'avez téléchargé, vous devez exécuter le script suivant sur 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'assemblage peut être accessible au serveur. Comme vous avez effectué toutes les étapes avec succès, vous pouvez utiliser la fonction suivante:
SELECT dbo.Concat(field1, ',')
FROM Table1
J'espère que ça aide !!!
J'utilise habituellement 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
Si vous souhaitez utiliser des valeurs NULL, vous pouvez le faire en ajoutant une clause where ou en ajoutant une autre COALESCE autour de la première.
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(COALESCE(@Names + ', ', '') + Name, @Names) FROM People
Exemple MySQL complet:
Nous avons des utilisateurs qui peuvent avoir beaucoup de 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);
Requête:
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
Dans Oracle, il s'agit de wm_concat
. Je pense que cette fonction est disponible dans les version 10g et les versions ultérieures.
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
retourne
Peter,Paul,Mary
Cette méthode s’applique à la base de données Teradata Aster uniquement dans la mesure où elle utilise sa fonction NPATH.
Encore une fois, nous avons des étudiants de table
SubjectID StudentName
---------- -------------
1 Mary
1 John
1 Sam
2 Alaina
2 Edward
Ensuite, avec NPATH, il n’ya qu’un seul SELECT:
SELECT * FROM npath(
ON Students
PARTITION BY SubjectID
ORDER BY StudentName
MODE(nonoverlapping)
PATTERN('A*')
SYMBOLS(
'true' as A
)
RESULT(
FIRST(SubjectID of A) as SubjectID,
ACCUMULATE(StudentName of A) as StudentName
)
);
Résultat:
SubjectID StudentName
---------- -------------
1 [John, Mary, Sam]
2 [Alaina, Edward]