Requête SQL pour concaténer les valeurs de colonne de plusieurs lignes dans Oracle

169

Serait-il possible de construire SQL pour concaténer les valeurs de colonne à partir de plusieurs lignes?

Ce qui suit est un exemple:

Tableau A

PID
UNE
B
C

Tableau B

Desc. PID SEQ

A 1 ont
A 2 un joli
A 3 jours.
B 1 Beau travail.
C 1 Oui
C 2 on peut 
C 3 faire 
C 4 ce travail!

La sortie du SQL doit être -

Desc PID
A Passez une bonne journée.
B Beau travail.
C Oui, nous pouvons faire ce travail!

Donc, fondamentalement, la colonne Desc pour la table de sortie est une concaténation des valeurs SEQ de la table B?

Une aide avec le SQL?

jagamot
la source
Voir par exemple: halisway.blogspot.com/2006/08/…
Andomar
Veuillez regarder cette solution . Cela vous sera utile.
Jineesh Uvantavida

Réponses:

237

Il existe plusieurs façons selon la version que vous avez - voir la documentation d'Oracle sur les techniques d'agrégation de chaînes . Un très courant est d'utiliser LISTAGG:

SELECT pid, LISTAGG(Desc, ' ') WITHIN GROUP (ORDER BY seq) AS description
FROM B GROUP BY pid;

Ensuite, rejoignez pour Achoisir celui que pidsvous voulez.

Remarque: prêt à l'emploi, LISTAGGne fonctionne correctement qu'avec les VARCHAR2colonnes.

Lou Franco
la source
2
l'utilisation de wm_concat () pour Oracle 10g concatène le texte dans l'ordre croissant du numéro de séquence délimité par des virgules, peut-on faire en décroissant délimité par autre chose?
jagamot
19

Il existe également une XMLAGGfonction, qui fonctionne sur les versions antérieures à 11.2. Étant donné qu'il WM_CONCATn'est ni documenté ni pris en charge par Oracle , il est recommandé de ne pas l'utiliser dans le système de production.

Avec XMLAGGvous pouvez faire ce qui suit:

SELECT XMLAGG(XMLELEMENT(E,ename||',')).EXTRACT('//text()') "Result" 
FROM employee_names

Ce que cela fait est

  • mettre les valeurs de la enamecolonne (concaténée avec une virgule) du employee_namestableau dans un élément xml (avec la balise E)
  • extraire le texte de ce
  • agréger le xml (concaténer)
  • appeler la colonne résultante "Résultat"
Peter
la source
XMLAGG fonctionne sur Oracle 12.2. De plus, XLMAGG permet de concataner des chaînes très longues que LISTAGG peut ne pas en raison de leur longueur finale.
Marco
13

Avec la clause de modèle SQL:

SQL> select pid
  2       , ltrim(sentence) sentence
  3    from ( select pid
  4                , seq
  5                , sentence
  6             from b
  7            model
  8                  partition by (pid)
  9                  dimension by (seq)
 10                  measures (descr,cast(null as varchar2(100)) as sentence)
 11                  ( sentence[any] order by seq desc
 12                    = descr[cv()] || ' ' || sentence[cv()+1]
 13                  )
 14         )
 15   where seq = 1
 16  /

P SENTENCE
- ---------------------------------------------------------------------------
A Have a nice day
B Nice Work.
C Yes we can do this work!

3 rows selected.

J'ai écrit à ce sujet ici . Et si vous suivez le lien vers le thread OTN, vous en trouverez d'autres, y compris une comparaison des performances.

Rob van Wijk
la source
10

La fonction analytique LISTAGG a été introduite dans Oracle 11g Release 2 , ce qui rend très facile l'agrégation des chaînes. Si vous utilisez 11g Release 2, vous devez utiliser cette fonction pour l'agrégation de chaînes. Veuillez vous référer à l'url ci-dessous pour plus d'informations sur la concaténation de chaînes.

http://www.oracle-base.com/articles/misc/StringAggregationTechniques.php

Concaténation de chaînes

Ashish J
la source
8

Comme la plupart des réponses le suggèrent, LISTAGGc'est l'option évidente. Cependant, un aspect ennuyeux avec LISTAGGest que si la longueur totale de la chaîne concaténée dépasse 4000 caractères (limite pour VARCHAR2SQL), l'erreur ci-dessous est renvoyée, ce qui est difficile à gérer dans les versions d'Oracle jusqu'à 12.1

ORA-01489: le résultat de la concaténation des chaînes est trop long

Une nouvelle fonctionnalité ajoutée dans 12cR2 est la ON OVERFLOWclause de LISTAGG. La requête comprenant cette clause ressemblerait à ceci:

SELECT pid, LISTAGG(Desc, ' ' on overflow truncate) WITHIN GROUP (ORDER BY seq) AS desc
FROM B GROUP BY pid;

Ce qui précède limitera la sortie à 4000 caractères mais ne lèvera pas l' ORA-01489erreur.

Voici quelques-unes des options supplémentaires de la ON OVERFLOWclause:

  • ON OVERFLOW TRUNCATE 'Contd..' : Ceci s'affichera 'Contd..'à la fin de la chaîne (la valeur par défaut est ...)
  • ON OVERFLOW TRUNCATE '' : Cela affichera les 4000 caractères sans aucune chaîne de fin.
  • ON OVERFLOW TRUNCATE WITH COUNT: Ceci affichera le nombre total de caractères à la fin après les caractères de fin. Par exemple: - ' ...(5512)'
  • ON OVERFLOW ERROR: Si vous vous attendez à ce que le LISTAGGéchoue avec l' ORA-01489erreur (qui est par défaut de toute façon).
Kaushik Nayak
la source
6

Pour ceux qui doivent résoudre ce problème à l'aide d'Oracle 9i (ou version antérieure), vous devrez probablement utiliser SYS_CONNECT_BY_PATH, car LISTAGG n'est pas disponible.

Pour répondre à l'OP, la requête suivante affichera le PID du tableau A et concaténera toutes les colonnes DESC du tableau B:

SELECT pid, SUBSTR (MAX (SYS_CONNECT_BY_PATH (description, ', ')), 3) all_descriptions
FROM (
       SELECT ROW_NUMBER () OVER (PARTITION BY pid ORDER BY pid, seq) rnum, pid, description
       FROM (
              SELECT a.pid, seq, description
              FROM table_a a, table_b b
              WHERE a.pid = b.pid(+)
             )
      )
START WITH rnum = 1
CONNECT BY PRIOR rnum = rnum - 1 AND PRIOR pid = pid
GROUP BY pid
ORDER BY pid;

Il peut également y avoir des cas où les clés et les valeurs sont toutes contenues dans une table. La requête suivante peut être utilisée lorsqu'il n'y a pas de table A et que seule la table B existe:

SELECT pid, SUBSTR (MAX (SYS_CONNECT_BY_PATH (description, ', ')), 3) all_descriptions
FROM (
       SELECT ROW_NUMBER () OVER (PARTITION BY pid ORDER BY pid, seq) rnum, pid, description
       FROM (
              SELECT pid, seq, description
              FROM table_b
             )
      )
START WITH rnum = 1
CONNECT BY PRIOR rnum = rnum - 1 AND PRIOR pid = pid
GROUP BY pid
ORDER BY pid;

Toutes les valeurs peuvent être réorganisées comme vous le souhaitez. Les descriptions concaténées individuelles peuvent être réorganisées dans la clause PARTITION BY, et la liste des PID peut être réorganisée dans la clause ORDER BY finale.


Alternativement: il peut arriver que vous souhaitiez concaténer toutes les valeurs d'une table entière en une seule ligne.

L'idée clé ici est d'utiliser une valeur artificielle pour le groupe de descriptions à concaténer.

Dans la requête suivante, la chaîne constante «1» est utilisée, mais toute valeur fonctionnera:

SELECT SUBSTR (MAX (SYS_CONNECT_BY_PATH (description, ', ')), 3) all_descriptions
FROM (
       SELECT ROW_NUMBER () OVER (PARTITION BY unique_id ORDER BY pid, seq) rnum, description
       FROM (
              SELECT '1' unique_id, b.pid, b.seq, b.description
              FROM table_b b
             )
      )
START WITH rnum = 1
CONNECT BY PRIOR rnum = rnum - 1;

Les descriptions concaténées individuelles peuvent être réorganisées dans la clause PARTITION BY.

Plusieurs autres réponses sur cette page ont également mentionné cette référence extrêmement utile: https://oracle-base.com/articles/misc/string-aggregation-techniques

JonathanDavidArndt
la source
3
  1. LISTAGG offre les meilleures performances si le tri est indispensable (00: 00: 05.85)

    SELECT pid, LISTAGG(Desc, ' ') WITHIN GROUP (ORDER BY seq) AS description FROM B GROUP BY pid;

  2. COLLECT offre les meilleures performances si le tri n'est pas nécessaire (00: 00: 02.90):

    SELECT pid, TO_STRING(CAST(COLLECT(Desc) AS varchar2_ntt)) AS Vals FROM B GROUP BY pid;

  3. COLLECT avec commande est un peu plus lent (00: 00: 07.08):

    SELECT pid, TO_STRING(CAST(COLLECT(Desc ORDER BY Desc) AS varchar2_ntt)) AS Vals FROM B GROUP BY pid;

Toutes les autres techniques étaient plus lentes.

Misho
la source
1
Il serait utile de développer votre réponse.
Jon Surrell
John, je ne voulais pas répéter l'article mais en bref voici les résultats: 1. LISTAGG offre les meilleures performances si le tri est un must (00: 00: 05.85) 2. COLLECT offre les meilleures performances si le tri ne l'est pas nécessaire (00: 00: 02.90): SELECT pid, TO_STRING (CAST (COLLECT (Desc) AS varchar2_ntt)) AS Vals FROM B GROUP BY pid; 3. COLLECT avec ordre est un peu plus lent (00: 00: 07.08): SELECT pid, TO_STRING (CAST (COLLECT (Desc ORDER BY Desc) AS varchar2_ntt)) AS Vals FROM B GROUP BY pid; Toutes les autres techniques étaient plus lentes.
Misho
1
Vous pouvez simplement modifier votre réponse pour inclure des informations pertinentes.
Jon Surrell
J'étais trop tard dans le montage et c'est pourquoi je l'ai ajouté à nouveau. Désolé, je suis nouveau ici et je commence juste à comprendre.
Misho
1

Avant d'exécuter une requête de sélection, exécutez ceci:

SET SERVEROUT ON SIZE 6000

SELECT XMLAGG(XMLELEMENT(E,SUPLR_SUPLR_ID||',')).EXTRACT('//text()') "SUPPLIER" 
FROM SUPPLIERS;
user2865810
la source
-1

Essayez ce code:

 SELECT XMLAGG(XMLELEMENT(E,fieldname||',')).EXTRACT('//text()') "FieldNames"
    FROM FIELD_MASTER
    WHERE FIELD_ID > 10 AND FIELD_AREA != 'NEBRASKA';
Krishnakumar MD Amain Infotech
la source
-3

Dans le sélectionnez où vous voulez votre concaténation, appelez une fonction SQL.

Par exemple:

select PID, dbo.MyConcat(PID)
   from TableA;

Puis pour la fonction SQL:

Function MyConcat(@PID varchar(10))
returns varchar(1000)
as
begin

declare @x varchar(1000);

select @x = isnull(@x +',', @x, @x +',') + Desc
  from TableB
    where PID = @PID;

return @x;

end

La syntaxe de l'en-tête de fonction peut être incorrecte, mais le principe fonctionne.

user5473005
la source
Ceci n'est pas valide pour Oracle
a_horse_with_no_name