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 ?

Était-ce utile?

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.

STRING_AGG (Transact-SQL)

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]
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top