it-swarm.dev

LISTAGG w Oracle, aby zwrócić różne wartości

Próbuję użyć funkcji LISTAGG w Oracle. Chciałbym uzyskać tylko różne wartości dla tej kolumny. Czy istnieje sposób, w jaki mogę uzyskać tylko różne wartości bez tworzenia funkcji lub procedury?

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

Muszę wybrać col1 i LISTAGG z col2 (kolumna 3 nie jest brana pod uwagę). Kiedy to robię, otrzymuję coś takiego w wyniku zmiennej LISTAGG: [2,2,3,4,5]  

Muszę tutaj usunąć duplikat „2”; Potrzebuję tylko różnych wartości col2 przeciwko col1.

71
Priyanth

Czy masz na myśli coś takiego:

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

Jeśli potrzebujesz więcej kolumn, coś takiego może być tym, czego szukasz:

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;
55

Oto jak rozwiązać problem.

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

from dual

zwraca

2,2.1,3,4 

ODPOWIEDŹ (patrz uwagi poniżej):

select col1, 

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

Uwaga: Powyższe będzie działać w większości przypadków - lista powinna być sortowana, może być konieczne przycięcie wszystkich końcowych i wiodących spacji w zależności od danych.

Jeśli masz wiele elementów w grupie> 20 lub duże rozmiary łańcuchów, które możesz napotkać w limicie rozmiaru łańcucha Oracle, wynik konkatenacji łańcucha jest zbyt długi. Tak więc wpisz maksymalną liczbę członków w każdej grupie. Będzie to działać tylko wtedy, gdy będzie w stanie wyświetlić tylko pierwszych członków. Jeśli masz bardzo długie łańcuchy zmienne, to może nie działać. będziesz musiał eksperymentować.

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;

Kolejne rozwiązanie (nie takie proste), miejmy nadzieję, że unikniemy limitu rozmiaru łańcucha Oracle - rozmiar łańcucha jest ograniczony do 4000. Dzięki temu postowi tutaj przez użytkownik3465996

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;

niektóre przypadki testowe - 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

elementy zawarte w elementach np. 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 dzięki Igor! działa we wszystkich przypadkach.

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
36
ozmike

możesz użyć nieudokumentowanej funkcji wm_concat.

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

ta funkcja zwraca kolumnę clob, jeśli chcesz, możesz użyć dbms_lob.substr, aby przekonwertować clob na varchar2.

10

Jeśli zamiarem jest zastosowanie tej transformacji do wielu kolumn, rozszerzyłem rozwiązanie_horse_with_no_name:

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

Jest to Oracle Database 11g Enterprise Edition Release 11.2.0.2.0 - 64-bitowa produkcja .
Nie mogłem użyć STRAGG, ponieważ nie ma sposobu na ODLEGŁOŚĆ i ZAMÓWIENIE .

Wydajność skaluje się liniowo, co jest dobre, ponieważ dodaję wszystkie interesujące kolumny. Powyższe zajęło 3 sekundy dla 77K rzędów. Tylko jeden zestaw, 0,172 sekundy. Robię to, ponieważ istnieje sposób na odróżnienie wielu kolumn w tabeli w jednym przebiegu.

6
Jeff Humphreys

Pokonałem ten problem, grupując najpierw wartości, a następnie wykonałem kolejną agregację z listagg. Coś takiego:

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)

tylko jeden pełny dostęp do tabeli, stosunkowo łatwy do rozszerzenia na bardziej złożone zapytania

6
RonaldM

Aby obejść problem z długością ciągu, możesz użyć zmiennej XMLAGG, która jest podobna do listagg, ale zwraca clob. 

Możesz następnie przeanalizować używając regexp_replace i uzyskać unikalne wartości, a następnie zamienić je z powrotem na łańcuch używając dbms_lob.substr(). Jeśli masz ogromną ilość różnych wartości, w ten sposób zabraknie ci miejsca, ale w wielu przypadkach poniższy kod powinien działać.

Możesz również zmienić używane ograniczniki. W moim przypadku chciałem '-' zamiast ',', ale powinieneś być w stanie zastąpić myślniki w moim kodzie i użyć przecinków, jeśli tego chcesz.

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
4
user3465996

Jeśli chcesz mieć różne wartości w wielu kolumnach, chcesz kontrolować porządek sortowania, nie chcesz używać nieudokumentowanej funkcji, która może zniknąć, i nie chcesz więcej niż jednego pełnego skanowania tabeli, może się okazać, że ta konstrukcja będzie przydatna:

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
);
4
user3853599

Co z utworzeniem dedykowanej funkcji, która sprawi, że „odrębna” część:

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;
/

A następnie użyj go do agregacji:

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

Nadchodzące Oracle 19c będzie obsługiwać DISTINCT z LISTAGG.

LISTAGG z opcją DISTINCT :

Ta funkcja nadchodzi z 19c:

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

EDYTOWAĆ:

Oracle 19C LISTAGG DISTINCT

Funkcja agregująca LISTAGG obsługuje teraz zduplikowaną eliminację przy użyciu nowego słowa kluczowego DISTINCT. Funkcja agregująca LISTAGG porządkuje wiersze dla każdej grupy w zapytaniu zgodnie z wyrażeniem ORDER BY, a następnie łączy wartości w pojedynczy ciąg. Dzięki nowemu słowu kluczowemu DISTINCT zduplikowane wartości można usunąć z określonego wyrażenia przed konkatenacją w pojedynczy ciąg. To eliminuje potrzebę tworzenia złożonego przetwarzania zapytań, aby znaleźć różne wartości przed użyciem agregującej funkcji LISTAGG. Dzięki opcji DISTINCT przetwarzanie w celu usunięcia duplikatów wartości można wykonać bezpośrednio w funkcji LISTAGG. Rezultatem jest prostszy, szybszy i bardziej wydajny SQL.

1
Lukasz Szozda

Jeśli nie potrzebujesz określonej kolejności połączonych wartości, a separatorem może być przecinek, możesz to zrobić:

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

Zaimplementowałem tę przechowywaną funkcję:

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;

Przykro mi, ale w niektórych przypadkach (dla bardzo dużego zestawu) Oracle może zwrócić ten błąd:

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.

ale myślę, że to dobry punkt wyjścia;)

0

Dalsze udoskonalanie korekcji @ YoYo do podejścia opartego na @ a_horse_with_no_name w wierszu numer_wiersza () przy użyciu DECODE vs CASE ( widziałem tutaj ). Widzę, że @Martin Vrbovsky również odpowiada na to pytanie.

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;
0
Beej

Możesz to zrobić za pomocą wymiany RegEx. Oto przykład:

-- 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;

Zamieszczono również tutaj: Oracle - unikalne wartości Listagg

0
DKroot

Użyj funkcji listagg_clob utworzonej w ten sposób:

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
cornel

listagg () ignoruje wartości NULL, więc w pierwszym kroku można użyć funkcji lag () do przeanalizowania, czy poprzedni rekord miał tę samą wartość, jeśli tak, to NULL, w przeciwnym razie „nowa wartość”.

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;

Wyniki 

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

Zauważ, że drugie 2 jest zastąpione przez NULL. Teraz możesz zawinąć SELECT wokół listagg ().

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 );

Wynik

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

Możesz to zrobić również na wielu kolumnach.

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 );

Wynik

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

Używanie SELECT DISTINCT ... jako części Subquery przed wywołaniem LISTAGG jest prawdopodobnie najlepszym sposobem na proste zapytania, jak zauważa @a_horse_w_nazwie

Jednak w bardziej złożonych zapytaniach osiągnięcie tego może być niemożliwe lub łatwe. Wymyśliłem to w scenariuszu, w którym zastosowano podejście top-n przy użyciu funkcji analitycznej.

Znalazłem więc funkcję agregującą COLLECT . Udokumentowano, że dostępny jest modyfikator UNIQUE lub DISTINCT. Tylko w 10g , cicho zawiedzie (ignoruje modyfikator bez błędu). Jednak, aby temu zaradzić, od inna odpowiedź doszedłem do tego rozwiązania:

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,
    ...
)

Zasadniczo, używając SET , usuwam duplikaty z mojej kolekcji.

Nadal będziesz musiał zdefiniować tab_typ jako podstawowy typ kolekcji, aw przypadku VARCHAR byłoby to na przykład:

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

Również jako poprawka do odpowiedzi z @a_horse_w_na_nazwie w sytuacji z wieloma kolumnami, gdzie możesz chcieć agregować nadal w trzeciej (lub więcej) kolumnach:

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;

Jeśli pozostawisz rn = 1 jako warunek where w zapytaniu, niepoprawnie agregujesz inne kolumny.

0
YoYo

Myślę, że może to pomóc - PRZYPADKUJ wartości kolumn do NULL, jeśli jest duplikatem - to nie jest dołączone do łańcucha LISTAGG:

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   

Prowadzi do:

COL1  ORIG         DISTINCT
1   2,2,3,4,5   2,3,4,5
2   5,6,6,6,7   5,6,7
0
Martin Vrbovsky

Jednym irytującym aspektem zmiennej LISTAGG jest to, że jeśli całkowita długość połączonego ciągu przekracza 4000 znaków (limit dla VARCHAR2 w SQL), zgłaszany jest poniższy błąd, który jest trudny do zarządzania w wersjach Oracle do 12.1 

ORA-01489: wynik konkatenacji łańcucha jest za długi

Nowa funkcja dodana w 12cR2 to klauzula ON OVERFLOW w zmiennej LISTAGG. Zapytanie zawierające tę klauzulę wyglądałoby tak:

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

Powyższe ograniczy wyjście do 4000 znaków, ale nie rzuci błędu ORA-01489.

Oto niektóre z dodatkowych opcji klauzuli ON OVERFLOW:

  • ON OVERFLOW TRUNCATE 'Contd..': Wyświetli 'Contd..' na koniec łańcucha (domyślnie to ...)
  • ON OVERFLOW TRUNCATE '': Wyświetli 4000 znaków bez ciągów końcowych.
  • ON OVERFLOW TRUNCATE WITH COUNT: Wyświetli sumę liczbę znaków na końcu po kończących znakach . Np .: - '...(5512)'
  • ON OVERFLOW ERROR: Jeśli spodziewasz się, że LISTAGG nie powiedzie się z błędem ORA-01489 (który i tak jest domyślny).
0
Kaushik Nayak

Najprostszym sposobem obsługi wielu listagg jest użycie 1 WITH (subquery factor) na kolumnę zawierającą listagg tej kolumny z select distinct:

    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)

Co daje:

col1  col2List  col3List
1     2,3,4,5   3,4,6
0
DS.

Potrzebuję DISTINCT tej wersji i mam ją opracowaną.

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

select col1, listaggr(col2,',') within group(Order by col2) from table group by col1 oznacza agregowanie ciągów (col2) do listy z zachowaniem kolejności n, a następnie postępowanie z duplikatami jako grupa po col1, co oznacza połączenie duplikatów col1 w 1 grupie. być może wygląda to na czyste i proste tak, jak powinno być, a jeśli chcesz również col3, po prostu musisz dodać jeszcze jedną listagg (), czyli select col1, listaggr(col2,',') within group(Order by col2),listaggr(col3,',') within group(order by col3) from table group by col1

0
Himanshu Ahuja

Napisałem funkcję do obsługi tego za pomocą wyrażeń regularnych. Dostępne są następujące parametry: 1) samo wywołanie listagg 2) Powtórzenie separatora

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;

Teraz nie musisz powtarzać wyrażenia regularnego za każdym razem, gdy to robisz, po prostu powiedz:

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

Czy ktoś myślał o użyciu klauzuli PARTITION BY? W tym zapytaniu udało mi się uzyskać listę usług aplikacji i dostęp.

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

Musiałem wyciąć klauzulę, gdzie dla NDA, ale masz pomysł.

0
James