web-dev-qa-db-fra.com

Comment combiner plusieurs lignes dans une liste délimitée par des virgules dans Oracle?

J'ai une requête simple:

select * from countries

avec les résultats suivants:

country_name
------------
Albania
Andorra
Antigua
.....

Je voudrais retourner les résultats dans une rangée, donc comme ceci:

Albania, Andorra, Antigua, ...

Bien sûr, je peux écrire une fonction PL/SQL pour faire le travail (je l’ai déjà fait dans Oracle 10g), mais existe-t-il une solution plus intéressante, de préférence non spécifique à Oracle (ou peut-être une fonction intégrée) pour cette tâche? ?

En général, je l'utilise pour éviter de créer plusieurs lignes dans une sous-requête. Par conséquent, si une personne a plus d'une citoyenneté, je ne veux pas qu'elle figure en double dans la liste.

Ma question est basée sur la question similaire sur SQL server 2005 .

UPDATE: Ma fonction ressemble à ceci:

CREATE OR REPLACE FUNCTION APPEND_FIELD (sqlstr in varchar2, sep in varchar2 ) return varchar2 is
ret varchar2(4000) := '';
TYPE cur_typ IS REF CURSOR;
rec cur_typ;
field varchar2(4000);
begin
     OPEN rec FOR sqlstr;
     LOOP
         FETCH rec INTO field;
         EXIT WHEN rec%NOTFOUND;
         ret := ret || field || sep;
     END LOOP;
     if length(ret) = 0 then
          RETURN '';
     else
          RETURN substr(ret,1,length(ret)-length(sep));
     end if;
end;
100
rics

Voici un moyen simple sans stragg ou créer une fonction.

create table countries ( country_name varchar2 (100));

insert into countries values ('Albania');

insert into countries values ('Andorra');

insert into countries values ('Antigua');


SELECT SUBSTR (SYS_CONNECT_BY_PATH (country_name , ','), 2) csv
      FROM (SELECT country_name , ROW_NUMBER () OVER (ORDER BY country_name ) rn,
                   COUNT (*) OVER () cnt
              FROM countries)
     WHERE rn = cnt
START WITH rn = 1
CONNECT BY rn = PRIOR rn + 1;

CSV                                                                             
--------------------------
Albania,Andorra,Antigua                                                         

1 row selected.

Comme d'autres l'ont mentionné, si vous utilisez une version 11g R2 ou supérieure, vous pouvez maintenant utiliser listagg, ce qui est beaucoup plus simple.

select listagg(country_name,', ') within group(order by country_name) csv
  from countries;

CSV                                                                             
--------------------------
Albania, Andorra, Antigua

1 row selected.
74
Daniel Emge

La fonction WM_CONCAT (si incluse dans votre base de données, antérieure à Oracle 11.2) ou LISTAGG (à partir d'Oracle 11.2) devrait suffire. Par exemple, ceci obtient une liste délimitée par des virgules des noms de table dans votre schéma:

select listagg(table_name, ', ') within group (order by table_name) 
  from user_tables;

ou

select wm_concat(table_name) 
  from user_tables;

Plus de détails/options

Lien vers la documentation

110
JoshL

Pour Oracle, vous pouvez utiliser LISTAGG

19
Makatun

Vous pouvez aussi utiliser ceci:

SELECT RTRIM (
          XMLAGG (XMLELEMENT (e, country_name || ',')).EXTRACT ('//text()'),
          ',')
          country_name
  FROM countries;
17
Decci.7

vous pouvez essayer cette requête.

select listagg(country_name,',') within group (order by country_name) cnt 
from countries; 
16
Gaya3

Le moyen le plus rapide consiste à utiliser la fonction de collecte Oracle.

Vous pouvez aussi faire ceci:

select *
  2    from (
  3  select deptno,
  4         case when row_number() over (partition by deptno order by ename)=1
  5             then stragg(ename) over
  6                  (partition by deptno
  7                       order by ename
  8                         rows between unbounded preceding
  9                                  and unbounded following)
 10         end enames
 11    from emp
 12         )
 13   where enames is not null

Visitez le site, demandez à tom et cherchez "stragg" ou "string concatenation". Beaucoup d'exemples. Il existe également une fonction Oracle non documentée pour répondre à vos besoins.

4
tuinstoel

J'ai eu besoin d'une chose semblable et a trouvé la solution suivante.

select RTRIM(XMLAGG(XMLELEMENT(e,country_name || ',')).EXTRACT('//text()'),',') country_name from  
2
tips

Dans cet exemple, nous créons une fonction pour apporter une liste délimitée par des virgules de motifs de blocage de facture AP au niveau de la ligne distincts dans un champ pour la requête au niveau de l'en-tête:

 FUNCTION getHoldReasonsByInvoiceId (p_InvoiceId IN NUMBER) RETURN VARCHAR2

  IS

  v_HoldReasons   VARCHAR2 (1000);

  v_Count         NUMBER := 0;

  CURSOR v_HoldsCusror (p2_InvoiceId IN NUMBER)
   IS
     SELECT DISTINCT hold_reason
       FROM ap.AP_HOLDS_ALL APH
      WHERE status_flag NOT IN ('R') AND invoice_id = p2_InvoiceId;
BEGIN

  v_HoldReasons := ' ';

  FOR rHR IN v_HoldsCusror (p_InvoiceId)
  LOOP
     v_Count := v_COunt + 1;

     IF (v_Count = 1)
     THEN
        v_HoldReasons := rHR.hold_reason;
     ELSE
        v_HoldReasons := v_HoldReasons || ', ' || rHR.hold_reason;
     END IF;
  END LOOP;

  RETURN v_HoldReasons;
END; 
2
David MacIntosh

J'ai toujours dû écrire un peu de PL/SQL pour cela ou je viens de concaténer un ',' dans le champ, de le copier dans un éditeur et de supprimer le CR de la liste en me donnant la ligne unique.

C'est,

select country_name||', ' country from countries

Un peu long dans les deux sens.

Si vous examinez Ask Tom, vous verrez des tonnes de solutions possibles, mais elles reviendront toutes aux déclarations de type et/ou PL/SQL.

demandez à Tom

1
Andrew Wood
SELECT REPLACE(REPLACE
((SELECT     TOP (100) PERCENT country_name + ', ' AS CountryName
FROM         country_name
ORDER BY country_name FOR XML PATH('')), 
'&<CountryName>', ''), '&<CountryName>', '') AS CountryNames
0
user1626874