LISTAGG in Oracle, um unterschiedliche Werte zurückzugeben


94

Ich versuche, die LISTAGGFunktion in Oracle zu verwenden. Ich möchte nur die unterschiedlichen Werte für diese Spalte erhalten. Gibt es eine Möglichkeit, nur die unterschiedlichen Werte abzurufen, ohne eine Funktion oder eine Prozedur zu erstellen?

  col1 col2 Erstellt von
   1 2 Smith 
   1 2 John 
   1 3 Ajay 
   1 4 Ram 
   1 5 Jack 

Ich muss col1 und das LISTAGGvon col2 auswählen (Spalte 3 wird nicht berücksichtigt). Wenn ich das mache, bekomme ich so etwas als Ergebnis von LISTAGG: [2,2,3,4,5]

Ich muss das Duplikat '2' hier entfernen. Ich brauche nur die unterschiedlichen Werte von col2 gegen col1.



Können Sie den erwarteten Ausbruch (Zeilen) aus der Stichprobe anzeigen? Was möchten Sie sehen, wenn es für col1 mehr als einen Wert gibt?
a_horse_with_no_name

Die erwartete Ausgabe des LISTAGG beträgt [2,3,4,5]. Die zweite '2' sollte entfernt werden. Und meine Tabelle hat mehr als 1000 Zeilen.
Priyanth

Was möchten Sie sehen, wenn es für col1 mehr als einen Wert gibt?
a_horse_with_no_name

Der Code sieht folgendermaßen aus: - SELECT col1, LISTAGG (col2, ',') innerhalb der Gruppe (Reihenfolge nach col2) FROM table T WHERE .... Es sollten also alle unterschiedlichen Werte von col2 angezeigt werden, die col1 entsprechen, getrennt durch Komma.
Priyanth

Antworten:


76

19c und später:

select listagg(distinct the_column, ',') within group (order by the_column)
from the_table

18c und früher:

select listagg(the_column, ',') within group (order by the_column)
from (
   select distinct the_column 
   from the_table
) t

Wenn Sie mehr Spalten benötigen, ist dies möglicherweise das, wonach Sie suchen:

select col1, listagg(col2, ',') within group (order by col2)
from (
  select col1, 
         col2,
         row_number() over (partition by col1, col2 order by col1) as rn
  from foo
  order by col1,col2
)
where rn = 1
group by col1;

2
Ähnlich wie ich es mir auch vorgestellt hatte. Wenn dies listaggdie einzige Aggregatfunktion in der Abfrage ist, sollte dies ausreichen. Die Kombination mit anderen Aggregatfunktionen ist jedoch schwieriger.
Andriy M

Ja. Meine Anfrage ist ähnlich.
Priyanth

1
@a_horse_with_no_name: Die obige select-Anweisung gibt doppelte Werte für mich an. Ich möchte die Duplikate entfernen. col1 col2 Erstellt von 1 2 Smith 1 2 John 1 3 Ajay 1 4 Ram 1 5 Jack Ich muss col1 und die LISTAGG von col2 auswählen (Spalte 3 wird nicht berücksichtigt). Während ich das mache, bekomme ich so etwas als Ergebnis von LISTAGG: -> [2,2,3,4,5] Ich muss das Duplikat '2' hier entfernen. Ich brauche nur die unterschiedlichen Werte von col2 gegen col1 .
Priyanth

@a_horse_with_no_name: Ich habe den Code- ausprobiert und die folgende Fehlermeldung erhalten: ORA-01489: Ergebnis der Zeichenfolgenverkettung ist zu lang 01489. 00000 - "Ergebnis der Zeichenfolgenverkettung ist zu lang" * Ursache: Das Ergebnis der Zeichenfolgenverkettung ist höher als das Maximum Größe.
Priyanth

@Priyanth: dann hast du kein Glück. Die Gesamtlänge überschreitet 4000 Byte und Oracle kann damit nicht umgehen. Sie müssen die Aggregation in Ihrem Anwendungscode durchführen.
a_horse_with_no_name

47

So lösen Sie Ihr Problem.

select  
      regexp_replace(
    '2,2,2.1,3,3,3,3,4,4' 
     ,'([^,]+)(,\1)*(,|$)', '\1\3')

from dual

kehrt zurück

2,2.1,3,4

Vom Orakel 19C wird in See gebaut hier

Ab 18C und früher versuchen Sie es innerhalb der Gruppe hier

Verwenden Sie andernfalls reguläre Ausdrücke

ANTWORT unten:

select col1, 

regexp_replace(
    listagg(
     col2 , ',') within group (order by col2)  -- sorted
    ,'([^,]+)(,\1)*(,|$)', '\1\3') )
   from tableX
where rn = 1
group by col1; 

Hinweis: Die oben genannten Funktionen funktionieren in den meisten Fällen. Die Liste sollte sortiert sein. Abhängig von Ihren Daten müssen Sie möglicherweise den gesamten nachfolgenden und führenden Speicherplatz kürzen.

Wenn Sie eine Vielzahl von Elementen in einer Gruppe> 20 oder großen Zeichenfolgen haben, kann es vorkommen, dass die Größe der Oracle-Zeichenfolge auf "Ergebnis der Verkettung von Zeichenfolgen zu lang" begrenzt ist.

Ab Oracle 12cR2 können Sie diesen Fehler unterdrücken, siehe hier . Alternativ können Sie den Mitgliedern in jeder Gruppe eine maximale Anzahl hinzufügen. Dies funktioniert nur, wenn es in Ordnung ist, nur die ersten Mitglieder aufzulisten. Wenn Sie sehr lange variable Zeichenfolgen haben, funktioniert dies möglicherweise nicht. Sie müssen experimentieren.

select col1,

case 
    when count(col2) < 100 then 
       regexp_replace(
        listagg(col2, ',') within group (order by col2)
        ,'([^,]+)(,\1)*(,|$)', '\1\3')

    else
    'Too many entries to list...'
end

from sometable
where rn = 1
group by col1;

Eine andere Lösung (nicht so einfach), um hoffentlich die Beschränkung der Orakel- Stringgröße zu vermeiden - die String-Größe ist auf 4000 begrenzt. Dank dieses Beitrags hier von user3465996

select col1  ,
    dbms_xmlgen.convert(  -- HTML decode
    dbms_lob.substr( -- limit size to 4000 chars
    ltrim( -- remove leading commas
    REGEXP_REPLACE(REPLACE(
         REPLACE(
           XMLAGG(
             XMLELEMENT("A",col2 )
               ORDER BY col2).getClobVal(),
             '<A>',','),
             '</A>',''),'([^,]+)(,\1)*(,|$)', '\1\3'),
                  ','), -- remove leading XML commas ltrim
                      4000,1) -- limit to 4000 string size
                      , 1)  -- HTML.decode
                       as col2
 from sometable
where rn = 1
group by col1;

V1 - einige Testfälle - FYI

regexp_replace('2,2,2.1,3,3,4,4','([^,]+)(,\1)+', '\1')
-> 2.1,3,4 Fail
regexp_replace('2 ,2 ,2.1,3 ,3 ,4 ,4 ','([^,]+)(,\1)+', '\1')
-> 2 ,2.1,3,4 Success  - fixed length items

V2-Elemente, die in Elementen enthalten sind, z. 2,21

regexp_replace('2.1,1','([^,]+)(,\1)+', '\1')
-> 2.1 Fail
regexp_replace('2 ,2 ,2.1,1 ,3 ,4 ,4 ','(^|,)(.+)(,\2)+', '\1\2')
-> 2 ,2.1,1 ,3 ,4  -- success - NEW regex
 regexp_replace('a,b,b,b,b,c','(^|,)(.+)(,\2)+', '\1\2')
-> a,b,b,c fail!

v3 - Regex danke Igor! funktioniert in allen Fällen.

select  
regexp_replace('2,2,2.1,3,3,4,4','([^,]+)(,\1)*(,|$)', '\1\3') ,
---> 2,2.1,3,4 works
regexp_replace('2.1,1','([^,]+)(,\1)*(,|$)', '\1\3'),
--> 2.1,1 works
regexp_replace('a,b,b,b,b,c','([^,]+)(,\1)*(,|$)', '\1\3')
---> a,b,c works

from dual

3
Faires Ergebnis, aber nicht so einfach. Bei ernsthaften Datengrößen werden Sie darauf stoßen ORA-01489: result of string concatenation is too long.
Pero

1
Ich würde es nicht als einfache, aber sehr attraktive Lösung bezeichnen. Ich wusste nicht, dass die Übereinstimmungsnummer in der Suchzeichenfolge verwendet werden kann, nicht nur in der Ersetzungszeichenfolge. Genial.
Peter Krassoi

1
Als Einschränkung erfordert diese Methode, dass die Werte sortiert werden, damit die duplizierten Werte aufeinander folgen. Sonst schlägt es fehl. Aber einfach ist gut! Und ich verwende diese Methode für meinen speziellen Fall. Vielen Dank!
StewS2

2
super einfach funktioniert nicht für mehr als 3 Wiederholungen! , zB a,b,b,b,b,cwürde werden a,b,b,c:-( (Oracle 11.2)
Andreas Dietrich

4
@ AndreasDietrich - Die folgende Lösung scheint immer richtig zu sein:regexp_replace(your_string, '([^,]+)(,\1)*(,|$)', '\1\3')
Egor Skriptunoff

10

Sie können die undokumentierte wm_concatFunktion verwenden.

select col1, wm_concat(distinct col2) col2_list 
from tab1
group by col1;

Diese Funktion gibt die Clob-Spalte zurück. Wenn Sie möchten, können Sie dbms_lob.substrClob in varchar2 konvertieren.


15
Nein, benutze das nicht.
Koshinae

1
Dies war genau das, was ich brauchte, und funktionierte perfekt in meiner vorhandenen aggregierten Abfrage, anstatt diese Abfrage in eine äußere zu verpacken. Was ist falsch an der Verwendung wm_concat(distinct x)?
Ehryk

1
weil es nicht dokumentiert ist und auf 12c nicht existiert. aber trotzdem denke ich, dass es bei alten Versionen der beste Weg ist.
Kemalettin Erbakırcı

1
Danke @ kemalettinerbakırcı! @thg Sie sollten berücksichtigen, dass Sie, wenn etwas nicht dokumentiert ist, nicht wissen, welche Nebenwirkungen es hat und welche anderen Dinge die Dokumentation Ihnen über die dokumentierten Funktionen sagt. Sie verwenden es einfach als Black Box und wissen nur, welcher Hebel was macht, basierend auf Folklore.
Koshinae


7

Ich habe dieses Problem überwunden, indem ich zuerst die Werte gruppiert und dann eine weitere Aggregation mit dem Listagg durchgeführt habe. Etwas wie das:

select a,b,listagg(c,',') within group(order by c) c, avg(d)
from (select a,b,c,avg(d)
      from   table
      group by (a,b,c))
group by (a,b)

Nur ein vollständiger Tabellenzugriff, relativ einfach auf komplexere Abfragen zu erweitern


6

Wenn diese Transformation auf mehrere Spalten angewendet werden soll, habe ich die Lösung von a_horse_with_no_name erweitert:

SELECT * FROM
(SELECT LISTAGG(GRADE_LEVEL, ',') within group(order by GRADE_LEVEL) "Grade Levels" FROM (select distinct GRADE_LEVEL FROM Students) t)                     t1,
(SELECT LISTAGG(ENROLL_STATUS, ',') within group(order by ENROLL_STATUS) "Enrollment Status" FROM (select distinct ENROLL_STATUS FROM Students) t)          t2,
(SELECT LISTAGG(GENDER, ',') within group(order by GENDER) "Legal Gender Code" FROM (select distinct GENDER FROM Students) t)                               t3,
(SELECT LISTAGG(CITY, ',') within group(order by CITY) "City" FROM (select distinct CITY FROM Students) t)                                                  t4,
(SELECT LISTAGG(ENTRYCODE, ',') within group(order by ENTRYCODE) "Entry Code" FROM (select distinct ENTRYCODE FROM Students) t)                             t5,
(SELECT LISTAGG(EXITCODE, ',') within group(order by EXITCODE) "Exit Code" FROM (select distinct EXITCODE FROM Students) t)                                 t6,
(SELECT LISTAGG(LUNCHSTATUS, ',') within group(order by LUNCHSTATUS) "Lunch Status" FROM (select distinct LUNCHSTATUS FROM Students) t)                     t7,
(SELECT LISTAGG(ETHNICITY, ',') within group(order by ETHNICITY) "Race Code" FROM (select distinct ETHNICITY FROM Students) t)                              t8,
(SELECT LISTAGG(CLASSOF, ',') within group(order by CLASSOF) "Expected Graduation Year" FROM (select distinct CLASSOF FROM Students) t)                     t9,
(SELECT LISTAGG(TRACK, ',') within group(order by TRACK) "Track Code" FROM (select distinct TRACK FROM Students) t)                                         t10,
(SELECT LISTAGG(GRADREQSETID, ',') within group(order by GRADREQSETID) "Graduation ID" FROM (select distinct GRADREQSETID FROM Students) t)                 t11,
(SELECT LISTAGG(ENROLLMENT_SCHOOLID, ',') within group(order by ENROLLMENT_SCHOOLID) "School Key" FROM (select distinct ENROLLMENT_SCHOOLID FROM Students) t)       t12,
(SELECT LISTAGG(FEDETHNICITY, ',') within group(order by FEDETHNICITY) "Federal Race Code" FROM (select distinct FEDETHNICITY FROM Students) t)                         t13,
(SELECT LISTAGG(SUMMERSCHOOLID, ',') within group(order by SUMMERSCHOOLID) "Summer School Key" FROM (select distinct SUMMERSCHOOLID FROM Students) t)                               t14,
(SELECT LISTAGG(FEDRACEDECLINE, ',') within group(order by FEDRACEDECLINE) "Student Decl to Prov Race Code" FROM (select distinct FEDRACEDECLINE FROM Students) t)          t15

Dies ist Oracle Database 11g Enterprise Edition Version 11.2.0.2.0 - 64-Bit-Produktion.
Ich konnte STRAGG nicht verwenden, da es keine Möglichkeit gibt, zu unterscheiden und zu bestellen.

Die Leistung skaliert linear, was gut ist, da ich alle Spalten von Interesse hinzufüge. Das oben Genannte dauerte 3 Sekunden für 77K-Zeilen. Für nur einen Rollup .172 Sekunden. Ich habe damit eine Möglichkeit gefunden, mehrere Spalten in einer Tabelle in einem Durchgang zu unterscheiden.


6

Wenn Sie unterschiedliche Werte für MEHRERE Spalten wünschen, die Sortierreihenfolge steuern möchten, keine undokumentierte Funktion verwenden möchten, die möglicherweise verschwindet, und nicht mehr als einen vollständigen Tabellenscan möchten, ist dieses Konstrukt möglicherweise hilfreich:

with test_data as 
(
      select 'A' as col1, 'T_a1' as col2, '123' as col3 from dual
union select 'A', 'T_a1', '456' from dual
union select 'A', 'T_a1', '789' from dual
union select 'A', 'T_a2', '123' from dual
union select 'A', 'T_a2', '456' from dual
union select 'A', 'T_a2', '111' from dual
union select 'A', 'T_a3', '999' from dual
union select 'B', 'T_a1', '123' from dual
union select 'B', 'T_b1', '740' from dual
union select 'B', 'T_b1', '846' from dual
)
select col1
     , (select listagg(column_value, ',') within group (order by column_value desc) from table(collect_col2)) as col2s
     , (select listagg(column_value, ',') within group (order by column_value desc) from table(collect_col3)) as col3s
from 
(
select col1
     , collect(distinct col2) as collect_col2
     , collect(distinct col3) as collect_col3
from test_data
group by col1
);

1
Sie können etwas mehr Zeit sparen, wenn Sie "union" durch "union all" ersetzen.
Burkay

4

Was ist mit der Erstellung einer dedizierten Funktion, die den "eindeutigen" Teil ausmacht:

create or replace function listagg_distinct (t in str_t, sep IN VARCHAR2 DEFAULT ',') 
  return VARCHAR2
as 
  l_rc VARCHAR2(4096) := '';
begin
  SELECT listagg(val, sep) WITHIN GROUP (ORDER BY 1)
    INTO l_rc
    FROM (SELECT DISTINCT column_value val FROM table(t));
  RETURN l_rc;
end;
/

Und dann verwenden Sie es, um die Aggregation durchzuführen:

SELECT col1, listagg_distinct(cast(collect(col_2) as str_t ), ', ')
  FROM your_table
  GROUP BY col_1;

4

Um das Problem mit der Zeichenfolgenlänge zu umgehen, können Sie verwenden XMLAGGlistagg umgehen, können Sie ein ähnliches das jedoch einen Clob zurückgibt.

Sie können dann mit analysieren regexp_replace und die eindeutigen Werte abrufen und sie dann mit wieder in eine Zeichenfolge umwandeln dbms_lob.substr(). Wenn Sie eine große Anzahl unterschiedlicher Werte haben, wird Ihnen auf diese Weise immer noch der Speicherplatz ausgehen, aber in vielen Fällen sollte der folgende Code funktionieren.

Sie können auch die von Ihnen verwendeten Trennzeichen ändern. In meinem Fall wollte ich '-' anstelle von ', aber Sie sollten in der Lage sein, die Bindestriche in meinem Code zu ersetzen und Kommas zu verwenden, wenn Sie das möchten.

select col1,
    dbms_lob.substr(ltrim(REGEXP_REPLACE(REPLACE(
         REPLACE(
           XMLAGG(
             XMLELEMENT("A",col2)
               ORDER BY col2).getClobVal(),
             '<A>','-'),
             '</A>',''),'([^-]*)(-\1)+($|-)', 
           '\1\3'),'-'), 4000,1) as platform_mix
from table

Dies ist eine großartige Idee, dbms_xmlgen.convert (string, 1) aufzurufen, um Konvertierungen und & -> & amp zu entfernen. Siehe meinen Beitrag Link
Ozmike

3

Weitere Verfeinerung der Korrektur von @ YoYo auf den auf row_number () basierenden Ansatz von @ a_horse_with_no_name unter Verwendung von DECODE vs CASE ( ich habe hier gesehen ). Ich sehe, dass @Martin Vrbovsky auch diese Antwort auf den Fallansatz hat.

select
  col1, 
  listagg(col2, ',') within group (order by col2) AS col2_list,
  listagg(col3, ',') within group (order by col3) AS col3_list,
  SUM(col4) AS col4
from (
  select
    col1, 
    decode(row_number() over (partition by col1, col2 order by null),1,col2) as col2,
    decode(row_number() over (partition by col1, col3 order by null),1,col3) as col3
  from foo
)
group by col1;

2

Die nächsten Oracle 19c unterstützt DISTINCTmit LISTAGG.

LISTAGG mit der Option DISTINCT :

Diese Funktion wird mit 19c geliefert:

SQL> select deptno, listagg (distinct sal,', ') within group (order by sal)  
  2  from scott.emp  
  3  group by deptno;  

BEARBEITEN:

Oracle 19C LISTAGG DISTINCT

Die LISTAGG-Aggregatfunktion unterstützt jetzt die Eliminierung von Duplikaten mithilfe des neuen Schlüsselworts DISTINCT. Die Aggregatfunktion LISTAGG ordnet die Zeilen für jede Gruppe in einer Abfrage gemäß dem ORDER BY-Ausdruck und verkettet dann die Werte zu einer einzelnen Zeichenfolge. Mit dem neuen Schlüsselwort DISTINCT können doppelte Werte aus dem angegebenen Ausdruck entfernt werden, bevor sie zu einer einzelnen Zeichenfolge verkettet werden. Dadurch entfällt die Notwendigkeit, eine komplexe Abfrageverarbeitung zu erstellen, um die unterschiedlichen Werte zu finden, bevor die aggregierte LISTAGG-Funktion verwendet wird. Mit der Option DISTINCT kann die Verarbeitung zum Entfernen doppelter Werte direkt in der Funktion LISTAGG durchgeführt werden. Das Ergebnis ist einfacheres, schnelleres und effizienteres SQL.


0

Hat jemand daran gedacht, eine PARTITION BY-Klausel zu verwenden? Bei dieser Abfrage hat es funktioniert, eine Liste der Anwendungsdienste und den Zugriff zu erhalten.

SELECT DISTINCT T.APP_SVC_ID, 
       LISTAGG(RTRIM(T.ACCESS_MODE), ',') WITHIN GROUP(ORDER BY T.ACCESS_MODE) OVER(PARTITION BY T.APP_SVC_ID) AS ACCESS_MODE 
  FROM APP_SVC_ACCESS_CNTL T 
 GROUP BY T.ACCESS_MODE, T.APP_SVC_ID

Ich musste meine where-Klausel für NDA herausschneiden, aber Sie haben die Idee.


Ich verstehe nicht, wie diese Abfrage unterschiedliche Elemente für die nimmt LISTAGG. Es scheint, dass Sie nur eine T.ACCESS_MODEpro Zeile haben würden, da Sie danach gruppieren?
jpmc26

0

Ich denke, dies könnte helfen - CASE den Spaltenwert auf NULL, wenn er doppelt vorhanden ist - dann wird er nicht an die LISTAGG-Zeichenfolge angehängt:

with test_data as 
(
      select 1 as col1, 2 as col2, 'Smith' as created_by from dual
union select 1, 2, 'John' from dual
union select 1, 3, 'Ajay' from dual
union select 1, 4, 'Ram' from dual
union select 1, 5, 'Jack' from dual
union select 2, 5, 'Smith' from dual
union select 2, 6, 'John' from dual
union select 2, 6, 'Ajay' from dual
union select 2, 6, 'Ram' from dual
union select 2, 7, 'Jack' from dual
)
SELECT col1  ,
      listagg(col2 , ',') within group (order by col2 ASC) AS orig_value,
      listagg(CASE WHEN rwn=1 THEN col2 END , ',') within group (order by col2 ASC) AS distinct_value
from 
    (
    select row_number() over (partition by col1,col2 order by 1) as rwn, 
           a.*
    from test_data a
    ) a
GROUP BY col1   

Ergebnisse in:

COL1  ORIG         DISTINCT
1   2,2,3,4,5   2,3,4,5
2   5,6,6,6,7   5,6,7

0

listagg () ignoriert NULL-Werte. In einem ersten Schritt können Sie mit der Funktion lag () analysieren, ob der vorherige Datensatz denselben Wert hat. Wenn ja, dann NULL, andernfalls 'neuer Wert'.

WITH tab AS 
(           
          SELECT 1 as col1, 2 as col2, 'Smith' as created_by FROM dual
UNION ALL SELECT 1 as col1, 2 as col2, 'John'  as created_by FROM dual
UNION ALL SELECT 1 as col1, 3 as col2, 'Ajay'  as created_by FROM dual
UNION ALL SELECT 1 as col1, 4 as col2, 'Ram'   as created_by FROM dual
UNION ALL SELECT 1 as col1, 5 as col2, 'Jack'  as created_by FROM dual
)
SELECT col1
     , CASE 
       WHEN lag(col2) OVER (ORDER BY col2) = col2 THEN 
         NULL 
       ELSE 
         col2 
       END as col2_with_nulls
     , created_by
  FROM tab;

Ergebnisse

      COL1 COL2_WITH_NULLS CREAT
---------- --------------- -----
         1               2 Smith
         1                 John
         1               3 Ajay
         1               4 Ram
         1               5 Jack

Beachten Sie, dass die zweite 2 durch NULL ersetzt wird. Jetzt können Sie ein SELECT mit dem listagg () umschließen.

WITH tab AS 
(           
          SELECT 1 as col1, 2 as col2, 'Smith' as created_by FROM dual
UNION ALL SELECT 1 as col1, 2 as col2, 'John'  as created_by FROM dual
UNION ALL SELECT 1 as col1, 3 as col2, 'Ajay'  as created_by FROM dual
UNION ALL SELECT 1 as col1, 4 as col2, 'Ram'   as created_by FROM dual
UNION ALL SELECT 1 as col1, 5 as col2, 'Jack'  as created_by FROM dual
)
SELECT listagg(col2_with_nulls, ',') WITHIN GROUP (ORDER BY col2_with_nulls) col2_list
  FROM ( SELECT col1
              , CASE WHEN lag(col2) OVER (ORDER BY col2) = col2 THEN NULL ELSE col2 END as col2_with_nulls
              , created_by
           FROM tab );

Ergebnis

COL2_LIST
---------
2,3,4,5

Sie können dies auch über mehrere Spalten hinweg tun.

WITH tab AS 
(           
          SELECT 1 as col1, 2 as col2, 'Smith' as created_by FROM dual
UNION ALL SELECT 1 as col1, 2 as col2, 'John'  as created_by FROM dual
UNION ALL SELECT 1 as col1, 3 as col2, 'Ajay'  as created_by FROM dual
UNION ALL SELECT 1 as col1, 4 as col2, 'Ram'   as created_by FROM dual
UNION ALL SELECT 1 as col1, 5 as col2, 'Jack'  as created_by FROM dual
)
SELECT listagg(col1_with_nulls, ',') WITHIN GROUP (ORDER BY col1_with_nulls) col1_list
     , listagg(col2_with_nulls, ',') WITHIN GROUP (ORDER BY col2_with_nulls) col2_list
     , listagg(created_by, ',')      WITHIN GROUP (ORDER BY created_by) created_by_list
  FROM ( SELECT CASE WHEN lag(col1) OVER (ORDER BY col1) = col1 THEN NULL ELSE col1 END as col1_with_nulls
              , CASE WHEN lag(col2) OVER (ORDER BY col2) = col2 THEN NULL ELSE col2 END as col2_with_nulls
              , created_by
           FROM tab );

Ergebnis

COL1_LIST COL2_LIST CREATED_BY_LIST
--------- --------- -------------------------
1         2,3,4,5   Ajay,Jack,John,Ram,Smith

0

Sie können dies über RegEx-Ersatz tun. Hier ist ein Beispiel:

-- Citations Per Year - Cited Publications main query. Includes list of unique associated core project numbers, ordered by core project number.
SELECT ptc.pmid AS pmid, ptc.pmc_id, ptc.pub_title AS pubtitle, ptc.author_list AS authorlist,
  ptc.pub_date AS pubdate,
  REGEXP_REPLACE( LISTAGG ( ppcc.admin_phs_org_code || 
    TO_CHAR(ppcc.serial_num,'FM000000'), ',') WITHIN GROUP (ORDER BY ppcc.admin_phs_org_code || 
    TO_CHAR(ppcc.serial_num,'FM000000')),
    '(^|,)(.+)(,\2)+', '\1\2')
  AS projectNum
FROM publication_total_citations ptc
  JOIN proj_paper_citation_counts ppcc
    ON ptc.pmid = ppcc.pmid
   AND ppcc.citation_year = 2013
  JOIN user_appls ua
    ON ppcc.admin_phs_org_code = ua.admin_phs_org_code
   AND ppcc.serial_num = ua.serial_num
   AND ua.login_id = 'EVANSF'
GROUP BY ptc.pmid, ptc.pmc_id, ptc.pub_title, ptc.author_list, ptc.pub_date
ORDER BY pmid;

Auch hier gepostet: Oracle - eindeutige Listagg-Werte


0

Verwenden Sie die folgende Funktion listagg_clob:

create or replace package list_const_p
is
list_sep varchar2(10) := ',';
end list_const_p;
/
sho err

create type listagg_clob_t as object(
v_liststring varchar2(32767),
v_clob clob,
v_templob number,

static function ODCIAggregateInitialize(
sctx IN OUT listagg_clob_t
) return number,
member function ODCIAggregateIterate(
self IN OUT listagg_clob_t, value IN varchar2
) return number,
member function ODCIAggregateTerminate(
self IN OUT listagg_clob_t, returnValue OUT clob, flags IN number
) return number,
member function ODCIAggregateMerge(
self IN OUT listagg_clob_t, ctx2 IN OUT listagg_clob_t
) return number
);
/
sho err

create or replace type body listagg_clob_t is

static function ODCIAggregateInitialize(sctx IN OUT listagg_clob_t)
return number is
begin
sctx := listagg_clob_t('', '', 0);
return ODCIConst.Success;
end;

member function ODCIAggregateIterate(
self IN OUT listagg_clob_t,
value IN varchar2
) return number is
begin
if nvl(lengthb(v_liststring),0) + nvl(lengthb(value),0) <= 4000 then
self.v_liststring:=self.v_liststring || value || list_const_p.list_sep;
else
if self.v_templob = 0 then
dbms_lob.createtemporary(self.v_clob, true, dbms_lob.call);
self.v_templob := 1;
end if;
dbms_lob.writeappend(self.v_clob, length(self.v_liststring), v_liststring);
self.v_liststring := value || list_const_p.list_sep;
end if;
return ODCIConst.Success;
end;

member function ODCIAggregateTerminate(
self IN OUT listagg_clob_t,
returnValue OUT clob,
flags IN number
) return number is
begin
if self.v_templob != 0 then
dbms_lob.writeappend(self.v_clob, length(self.v_liststring), self.v_liststring);
dbms_lob.trim(self.v_clob, dbms_lob.getlength(self.v_clob) - 1);
else
self.v_clob := substr(self.v_liststring, 1, length(self.v_liststring) - 1);
end if;
returnValue := self.v_clob;
return ODCIConst.Success;
end;

member function ODCIAggregateMerge(self IN OUT listagg_clob_t, ctx2 IN OUT listagg_clob_t) return number is
begin
if ctx2.v_templob != 0 then
if self.v_templob != 0 then
dbms_lob.append(self.v_clob, ctx2.v_clob);
dbms_lob.freetemporary(ctx2.v_clob);
ctx2.v_templob := 0;
else
self.v_clob := ctx2.v_clob;
self.v_templob := 1;
ctx2.v_clob := '';
ctx2.v_templob := 0;
end if;
end if;
if nvl(lengthb(self.v_liststring),0) + nvl(lengthb(ctx2.v_liststring),0) <= 4000 then
self.v_liststring := self.v_liststring || ctx2.v_liststring;
ctx2.v_liststring := '';
else
if self.v_templob = 0 then
dbms_lob.createtemporary(self.v_clob, true, dbms_lob.call);
self.v_templob := 1;
end if;
dbms_lob.writeappend(self.v_clob, length(self.v_liststring), self.v_liststring);
dbms_lob.writeappend(self.v_clob, length(ctx2.v_liststring), ctx2.v_liststring);
self.v_liststring := '';
ctx2.v_liststring := '';
end if;
return ODCIConst.Success;
end;
end;
/
sho err

CREATE or replace FUNCTION listagg_clob (input varchar2) RETURN clob
PARALLEL_ENABLE AGGREGATE USING listagg_clob_t;
/
sho err 


0

Ich habe eine Funktion geschrieben, um dies mit regulären Ausdrücken zu handhaben. Die in-Parameter sind: 1) der Listagg-Aufruf selbst 2) eine Wiederholung des Trennzeichens

create or replace function distinct_listagg
  (listagg_in varchar2,
   delimiter_in varchar2)

   return varchar2
   as
   hold_result varchar2(4000);
   begin

   select rtrim( regexp_replace( (listagg_in)
      , '([^'||delimiter_in||']*)('||
      delimiter_in||'\1)+($|'||delimiter_in||')', '\1\3'), ',')
      into hold_result
      from dual;

return hold_result;

end;

Jetzt müssen Sie den regulären Ausdruck nicht jedes Mal wiederholen, indem Sie einfach sagen:

select distinct_listagg(
                       listagg(myfield,', ') within group (order by 1),
                       ', '
                       )
     from mytable;

0

Wenn Sie keine bestimmte Reihenfolge verketteter Werte benötigen und das Trennzeichen ein Komma sein kann, können Sie Folgendes tun:

select col1, stragg(distinct col2)
  from table
 group by col1

0

Ich brauchte eine DISTINCT-Version davon und ließ diese funktionieren.

RTRIM(REGEXP_REPLACE(
                       (value, ', ') WITHIN GROUP( ORDER BY value)), 
                            '([^ ]+)(, \1)+','\1'),', ') 

0

Ein ärgerlicher Aspekt dabei LISTAGGist, dass VARCHAR2der folgende Fehler ausgelöst wird, wenn die Gesamtlänge der verketteten Zeichenfolge 4000 Zeichen überschreitet (Limit für in SQL), was in Oracle-Versionen bis 12.1 schwierig zu verwalten ist

ORA-01489: Ergebnis der Zeichenfolgenverkettung ist zu lang

Eine neue Funktion, die in 12cR2 hinzugefügt wurde, ist die ON OVERFLOWKlausel von LISTAGG. Die Abfrage mit dieser Klausel würde folgendermaßen aussehen:

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

Das Obige beschränkt die Ausgabe auf 4000 Zeichen, wirft aber nicht die ORA-01489 Fehler aus.

Dies sind einige der zusätzlichen Optionen der ON OVERFLOWKlausel:

  • ON OVERFLOW TRUNCATE 'Contd..' : Dies wird 'Contd..'am Ende der Zeichenfolge angezeigt (Standard ist... )
  • ON OVERFLOW TRUNCATE '' : Dadurch werden die 4000 Zeichen ohne Abschlusszeichenfolge angezeigt.
  • ON OVERFLOW TRUNCATE WITH COUNT: Hiermit wird die Gesamtzahl der Zeichen am Ende nach den abschließenden Zeichen angezeigt. Z.B:- '...(5512) '
  • ON OVERFLOW ERROR: Wenn Sie erwarten LISTAGG, dass das mit dem ORA-01489Fehler fehlschlägt (was sowieso Standard ist).

0

Ich habe diese gespeicherte Funktion implementiert:

CREATE TYPE LISTAGG_DISTINCT_PARAMS AS OBJECT (ELEMENTO VARCHAR2(2000), SEPARATORE VARCHAR2(10));

CREATE TYPE T_LISTA_ELEMENTI AS TABLE OF VARCHAR2(2000);

CREATE TYPE T_LISTAGG_DISTINCT AS OBJECT (

    LISTA_ELEMENTI T_LISTA_ELEMENTI,
        SEPARATORE VARCHAR2(10),

    STATIC FUNCTION ODCIAGGREGATEINITIALIZE(SCTX  IN OUT            T_LISTAGG_DISTINCT) 
                    RETURN NUMBER,

    MEMBER FUNCTION ODCIAGGREGATEITERATE   (SELF  IN OUT            T_LISTAGG_DISTINCT, 
                                            VALUE IN                    LISTAGG_DISTINCT_PARAMS ) 
                    RETURN NUMBER,

    MEMBER FUNCTION ODCIAGGREGATETERMINATE (SELF         IN     T_LISTAGG_DISTINCT,
                                            RETURN_VALUE OUT    VARCHAR2, 
                                            FLAGS        IN     NUMBER      )
                    RETURN NUMBER,

    MEMBER FUNCTION ODCIAGGREGATEMERGE       (SELF               IN OUT T_LISTAGG_DISTINCT,
                                                                                        CTX2                 IN         T_LISTAGG_DISTINCT    )
                    RETURN NUMBER
);

CREATE OR REPLACE TYPE BODY T_LISTAGG_DISTINCT IS 

    STATIC FUNCTION ODCIAGGREGATEINITIALIZE(SCTX IN OUT T_LISTAGG_DISTINCT) RETURN NUMBER IS 
    BEGIN
                SCTX := T_LISTAGG_DISTINCT(T_LISTA_ELEMENTI() , ',');
        RETURN ODCICONST.SUCCESS;
    END;

    MEMBER FUNCTION ODCIAGGREGATEITERATE(SELF IN OUT T_LISTAGG_DISTINCT, VALUE IN LISTAGG_DISTINCT_PARAMS) RETURN NUMBER IS
    BEGIN

                IF VALUE.ELEMENTO IS NOT NULL THEN
                        SELF.LISTA_ELEMENTI.EXTEND;
                        SELF.LISTA_ELEMENTI(SELF.LISTA_ELEMENTI.LAST) := TO_CHAR(VALUE.ELEMENTO);
                        SELF.LISTA_ELEMENTI:= SELF.LISTA_ELEMENTI MULTISET UNION DISTINCT SELF.LISTA_ELEMENTI;
                        SELF.SEPARATORE := VALUE.SEPARATORE;
                END IF;
        RETURN ODCICONST.SUCCESS;
    END;

    MEMBER FUNCTION ODCIAGGREGATETERMINATE(SELF IN T_LISTAGG_DISTINCT, RETURN_VALUE OUT VARCHAR2, FLAGS IN NUMBER) RETURN NUMBER IS
      STRINGA_OUTPUT            CLOB:='';
            LISTA_OUTPUT                T_LISTA_ELEMENTI;
            TERMINATORE                 VARCHAR2(3):='...';
            LUNGHEZZA_MAX           NUMBER:=4000;
    BEGIN

                IF SELF.LISTA_ELEMENTI.EXISTS(1) THEN -- se esiste almeno un elemento nella lista

                        -- inizializza una nuova lista di appoggio
                        LISTA_OUTPUT := T_LISTA_ELEMENTI();

                        -- riversamento dei soli elementi in DISTINCT
                        LISTA_OUTPUT := SELF.LISTA_ELEMENTI MULTISET UNION DISTINCT SELF.LISTA_ELEMENTI;

                        -- ordinamento degli elementi
                        SELECT CAST(MULTISET(SELECT * FROM TABLE(LISTA_OUTPUT) ORDER BY 1 ) AS T_LISTA_ELEMENTI ) INTO LISTA_OUTPUT FROM DUAL;

                        -- concatenazione in una stringa                        
                        FOR I IN LISTA_OUTPUT.FIRST .. LISTA_OUTPUT.LAST - 1
                        LOOP
                            STRINGA_OUTPUT := STRINGA_OUTPUT || LISTA_OUTPUT(I) || SELF.SEPARATORE;
                        END LOOP;
                        STRINGA_OUTPUT := STRINGA_OUTPUT || LISTA_OUTPUT(LISTA_OUTPUT.LAST);

                        -- se la stringa supera la dimensione massima impostata, tronca e termina con un terminatore
                        IF LENGTH(STRINGA_OUTPUT) > LUNGHEZZA_MAX THEN
                                    RETURN_VALUE := SUBSTR(STRINGA_OUTPUT, 0, LUNGHEZZA_MAX - LENGTH(TERMINATORE)) || TERMINATORE;
                        ELSE
                                    RETURN_VALUE:=STRINGA_OUTPUT;
                        END IF;

                ELSE -- se non esiste nessun elemento, restituisci NULL

                        RETURN_VALUE := NULL;

                END IF;

        RETURN ODCICONST.SUCCESS;
    END;

    MEMBER FUNCTION ODCIAGGREGATEMERGE(SELF IN OUT T_LISTAGG_DISTINCT, CTX2 IN T_LISTAGG_DISTINCT) RETURN NUMBER IS
    BEGIN
        RETURN ODCICONST.SUCCESS;
    END;

END; -- fine corpo

CREATE
FUNCTION LISTAGG_DISTINCT (INPUT LISTAGG_DISTINCT_PARAMS) RETURN VARCHAR2
    PARALLEL_ENABLE AGGREGATE USING T_LISTAGG_DISTINCT;

// Example
SELECT LISTAGG_DISTINCT(LISTAGG_DISTINCT_PARAMS(OWNER, ', ')) AS LISTA_OWNER
FROM SYS.ALL_OBJECTS;

Es tut mir leid, aber in einigen Fällen (für einen sehr großen Satz) könnte Oracle diesen Fehler zurückgeben:

Object or Collection value was too large. The size of the value
might have exceeded 30k in a SORT context, or the size might be
too big for available memory.

aber ich denke das ist ein guter Ausgangspunkt;)


0

select col1, listaggr(col2,',') within group(Order by col2) from table group by col1 Dies bedeutet, dass die Zeichenfolgen (Spalte 2) in einer Liste zusammengefasst werden, wobei die Reihenfolge n beibehalten wird. Anschließend werden die Duplikate als Gruppe mit Spalte 1 behandelt. Dies bedeutet, dass Spalten-Duplikate in einer Gruppe zusammengeführt werden. Vielleicht sieht das so sauber und einfach aus, wie es sein sollte, und wenn Sie auch col3 wollen, müssen Sie nur noch ein listagg () hinzufügenselect col1, listaggr(col2,',') within group(Order by col2),listaggr(col3,',') within group(order by col3) from table group by col1


0

Verwenden von SELECT DISTINCT ... als Teil einer Unterabfrage vor dem Aufruf von LISTAGG ist wahrscheinlich der beste Weg für einfache Abfragen, wie unter @a_horse_with_no_name angegeben

Bei komplexeren Abfragen ist dies jedoch möglicherweise nicht oder nicht einfach möglich. Ich hatte dies in einem Szenario, das einen Top-n-Ansatz unter Verwendung einer Analysefunktion verwendete.

Also habe ich die COLLECTAggregatfunktion gefunden. Es ist dokumentiert, dass der Modifikator UNIQUEoder DISTINCTverfügbar ist. Nur in 10g schlägt es leise fehl (es ignoriert den Modifikator ohne Fehler). Um dies zu überwinden, kam ich aus einer anderen Antwort zu dieser Lösung:

SELECT
  ...
  (
    SELECT LISTAGG(v.column_value,',') WITHIN GROUP (ORDER BY v.column_value)
    FROM TABLE(columns_tab) v
  ) AS columns,
  ...
FROM (
  SELECT
    ...
    SET(CAST(COLLECT(UNIQUE some_column ORDER BY some_column) AS tab_typ)) AS columns_tab,
    ...
)

Grundsätzlich SETentferne ich mithilfe von die Duplikate in meiner Sammlung.

Sie müssten den noch tab_typals grundlegenden Sammlungstyp definieren , und im Fall von a VARCHARwäre dies zum Beispiel:

CREATE OR REPLACE type tab_typ as table of varchar2(100)
/

Auch als Korrektur für die Antwort von @a_horse_with_no_name in der Situation mit mehreren Spalten, in der Sie möglicherweise noch eine dritte (oder mehrere) Spalten zusammenfassen möchten:

select
  col1, 
  listagg(CASE rn2 WHEN 1 THEN col2 END, ',') within group (order by col2) AS col2_list,
  listagg(CASE rn3 WHEN 1 THEN col3 END, ',') within group (order by col3) AS col3_list,
  SUM(col4) AS col4
from (
  select
    col1, 
    col2,
    row_number() over (partition by col1, col2 order by null) as rn2,
    row_number() over (partition by col1, col3 order by null) as rn3
  from foo
)
group by col1;

Wenn Sie die Abfragebedingung " rn = 1where" der Abfrage überlassen würden , würden Sie andere Spalten falsch aggregieren.


0

Sehr einfach - verwenden Sie in Ihrer Abfrage eine Unterabfrage mit einem ausgewählten Unterschied:

SELECT question_id,
       LISTAGG(element_id, ',') WITHIN GROUP (ORDER BY element_id)
FROM
       (SELECT distinct question_id, element_id
       FROM YOUR_TABLE)
GROUP BY question_id;

-1

Die einfachste Möglichkeit, mit mehreren Listaggs umzugehen, besteht darin, 1 WITH (Unterabfragefaktor) pro Spalte zu verwenden, die einen Listagg dieser Spalte aus einer ausgewählten Auswahl enthält:

    WITH tab AS 
    (           
        SELECT 1 as col1, 2 as col2, 3 as col3, 'Smith' as created_by FROM dual
        UNION ALL SELECT 1 as col1, 2 as col2, 3 as col3,'John'  as created_by FROM dual
        UNION ALL SELECT 1 as col1, 3 as col2, 4 as col3,'Ajay'  as created_by FROM dual
        UNION ALL SELECT 1 as col1, 4 as col2, 4 as col3,'Ram'   as created_by FROM dual
        UNION ALL SELECT 1 as col1, 5 as col2, 6 as col3,'Jack'  as created_by FROM dual
    )
    , getCol2 AS
    (
        SELECT  DISTINCT col1, listagg(col2,',') within group (order by col2)  over (partition by col1) AS col2List
        FROM ( SELECT DISTINCT col1,col2 FROM tab)
    )
    , getCol3 AS
    (
        SELECT  DISTINCT col1, listagg(col3,',') within group (order by col3)  over (partition by col1) AS col3List
        FROM ( SELECT DISTINCT col1,col3 FROM tab)
    )
    select col1,col2List,col3List
    FROM getCol2
    JOIN getCol3
    using (col1)

Welches gibt:

col1  col2List  col3List
1     2,3,4,5   3,4,6
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.