Próbuję użyć LISTAGG
funkcji 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 Jana 1 3 Ajay 1 4 Ram 1 5 Jack
Muszę wybrać col1 i LISTAGG
col2 (kolumna 3 nie jest brana pod uwagę). Kiedy to robię, otrzymuję coś takiego w wyniku LISTAGG
: [2,2,3,4,5]
Muszę tutaj usunąć zduplikowaną cyfrę „2”; Potrzebuję tylko różnych wartości col2 przeciwko col1.
sql
oracle
aggregate-functions
listagg
Priyanth
źródło
źródło
Odpowiedzi:
19c i nowsze:
select listagg(distinct the_column, ',') within group (order by the_column) from the_table
18c i starsze:
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;
źródło
listagg
jest jedyną funkcją agregującą w zapytaniu, powinno to wystarczyć. Jednak połączenie go z innymi funkcjami agregującymi jest trudniejsze.Oto jak rozwiązać problem.
select regexp_replace( '2,2,2.1,3,3,3,3,4,4' ,'([^,]+)(,\1)*(,|$)', '\1\3') from dual
zwroty
2,2.1,3,4
Od Oracle 19C jest zbudowany w patrz tutaj
Od 18 ° C i wcześniej spróbuj w grupie, patrz tutaj
W przeciwnym razie użyj wyrażeń regularnych
Odpowiedź 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 zadziała w większości przypadków - lista powinna być posortowana, może być konieczne przycięcie całej końcowej i początkowej spacji w zależności od danych.
Jeśli masz dużo elementów w grupie> 20 lub duże ciągi znaków, możesz napotkać limit rozmiaru łańcucha Oracle „wynik konkatenacji ciągów jest za długi”.
Z Oracle 12cR2 możesz pominąć ten błąd, patrz tutaj . Ewentualnie podaj maksymalną liczbę członków w każdej grupie. To zadziała tylko wtedy, gdy możesz podać tylko pierwszych członków. Jeśli masz bardzo długie ciągi zmiennych, może to 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;
Innym rozwiązaniem (nie takie proste), aby uniknąć nadzieją oracle limit rozmiaru String - Ciąg wielkość jest ograniczona do 4000. Dzięki tym poście tutaj przez 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 - kilka przypadków testowych - FYI
V2 - pozycje zawarte w pozycjach 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 Igorowi! 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
źródło
ORA-01489: result of string concatenation is too long
.a,b,b,b,b,c
stanie sięa,b,b,c
:-( (Oracle 11.2)regexp_replace(your_string, '([^,]+)(,\1)*(,|$)', '\1\3')
możesz użyć
wm_concat
funkcji nieudokumentowanej .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ć jej
dbms_lob.substr
do konwersji clob na varchar2.źródło
wm_concat(distinct x)
?wm_concat
. Zobacz Dlaczego nie używać funkcji WM_CONCAT w Oracle? .Rozwiązałem ten problem, najpierw grupując wartości, a następnie wykonując 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 do bardziej złożonych zapytań
źródło
Jeśli zamierzam zastosować tę transformację do wielu kolumn, rozszerzyłem rozwiązanie a_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
To jest Oracle Database 11g Enterprise Edition w wersji 11.2.0.2.0 - produkcja 64-bitowa.
Nie mogłem użyć STRAGG, ponieważ nie ma możliwości ROZRÓŻNIENIA i ZAMÓWIENIA.
Wydajność skaluje się liniowo, co jest dobre, ponieważ dodaję wszystkie interesujące kolumny. Powyższe zajęło 3 sekundy dla 77K rzędów. Za jeden pakiet zbiorczy 0,172 sekundy. Chodziło mi o to, że był sposób na rozróżnienie wielu kolumn w tabeli w jednym przebiegu.
źródło
Jeśli chcesz mieć odrębne wartości w MULTIPLE kolumnach, chcesz mieć kontrolę nad porządkiem sortowania, nie chcesz używać nieudokumentowanej funkcji, która może zniknąć, i nie chcesz więcej niż jednego pełnego skanowania tabeli, ta konstrukcja może być 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 );
źródło
A co z utworzeniem dedykowanej funkcji, która będzie stanowić „odrębną” 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 wykonania agregacji:
SELECT col1, listagg_distinct(cast(collect(col_2) as str_t ), ', ') FROM your_table GROUP BY col_1;
źródło
Aby obejść problem z długością struny, możesz użyć
XMLAGG
podobnego do,listagg
ale zwraca clob.Możesz następnie przeanalizować używając
regexp_replace
i pobrać unikalne wartości, a następnie przekształcić je z powrotem w ciąg przy użyciudbms_lob.substr()
. Jeśli masz dużą liczbę różnych wartości, w ten sposób nadal zabraknie Ci miejsca, ale w wielu przypadkach poniższy kod powinien działać.Możesz także zmienić używane separatory. W moim przypadku chciałem '-' zamiast ',' ale powinieneś być w stanie zamienić myślniki w moim kodzie i użyć przecinków, jeśli 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
źródło
Dalsze udoskonalanie korekty @ YoYo do podejścia opartego na row_number () @ a_horse_with_no_name przy użyciu DECODE vs CASE ( widziałem tutaj ). Widzę, że @Martin Vrbovsky również ma takie podejście do sprawy.
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;
źródło
Nadchodzące Oracle 19c wesprze
DISTINCT
zLISTAGG
.EDYTOWAĆ:
Oracle 19C LISTAGG DISTINCT
źródło
Czy ktoś pomyślał o użyciu klauzuli PARTITION BY? W przypadku tego zapytania zadziałało uzyskanie listy usług aplikacji i dostępu.
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 usunąć moją klauzulę gdzie dla NDA, ale masz pomysł.
źródło
LISTAGG
. Wygląda na to, że masz tylko jedenT.ACCESS_MODE
wiersz w każdym wierszu, ponieważ grupujesz według niego?Myślę, że może to pomóc - PRZYPISZ wartość kolumny na NULL, jeśli jest zduplikowana - wtedy nie jest dołączana do ciągu 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:
źródło
listagg () ignoruje wartości NULL, więc w pierwszym kroku możesz 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
Zwróć uwagę, że drugie 2 jest zastępowane przez NULL. Teraz możesz owinąć wokół niego SELECT listąagg ().
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ż w 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
źródło
Możesz to zrobić poprzez wymianę 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;
Opublikowane również tutaj: Oracle - unikalne wartości Listagg
źródło
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
źródło
Napisałem funkcję do obsługi tego za pomocą wyrażeń regularnych. Parametry in to: 1) samo wywołanie listagg 2) Powtórzenie ogranicznika
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;
źródło
Jeśli nie potrzebujesz określonej kolejności łączonych wartości, a separatorem może być przecinek, możesz:
select col1, stragg(distinct col2) from table group by col1
źródło
Potrzebowałem DISTINCT wersji tego i sprawiłem, że ta działa.
źródło
Irytującym aspektem
LISTAGG
jest to, że jeśli całkowita długość połączonego ciągu przekracza 4000 znaków (limit dlaVARCHAR2
SQL), generowany jest poniższy błąd, który jest trudny do zarządzania w wersjach Oracle do 12.1Nową funkcją dodaną w 12cR2 jest
ON OVERFLOW
klauzulaLISTAGG
. Zapytanie zawierające tę klauzulę wyglądałoby następująco:SELECT pid, LISTAGG(Desc, ' ' on overflow truncate) WITHIN GROUP (ORDER BY seq) AS desc FROM B GROUP BY pid;
Powyższe ograniczy wynik do 4000 znaków, ale nie spowoduje zgłoszenia
ORA-01489
błędu.Oto niektóre z dodatkowych opcji
ON OVERFLOW
klauzuli:ON OVERFLOW TRUNCATE 'Contd..'
: Wyświetli się'Contd..'
na końcu ciągu (domyślnie...
)ON OVERFLOW TRUNCATE ''
: Spowoduje to wyświetlenie 4000 znaków bez żadnego łańcucha kończącego.ON OVERFLOW TRUNCATE WITH COUNT
: Wyświetli całkowitą liczbę znaków na końcu po znakach kończących. Np .: - '...(5512)
'ON OVERFLOW ERROR
: Jeśli spodziewaszLISTAGG
się niepowodzenia z powoduORA-01489
błędu (co i tak jest domyślne).źródło
Zaimplementowałem tę zapisaną 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;
Przepraszam, ale w niektórych przypadkach (dla bardzo dużego zestawu) Oracle może zwrócić ten błąd:
ale myślę, że to dobry punkt wyjścia;)
źródło
select col1, listaggr(col2,',') within group(Order by col2) from table group by col1
co oznacza agregację ciągów (col2) w listę, zachowując kolejność n, a następnie zajmie się duplikatami jako grupowaniem według col1, co oznacza połączenie duplikatów col1 w 1 grupę. być może wygląda to czysto i prosto, jak powinno, a jeśli chcesz również col3, po prostu musisz dodać jeszcze jedną listagg (), to znaczyselect col1, listaggr(col2,',') within group(Order by col2),listaggr(col3,',') within group(order by col3) from table group by col1
źródło
Używanie
SELECT DISTINCT ...
jako części podzapytania przed wywołaniem LISTAGG jest prawdopodobnie najlepszym sposobem na proste zapytania, jak zauważył @a_horse_with_no_nameJednak w przypadku bardziej złożonych zapytań może to nie być możliwe lub łatwe do osiągnięcia. Wpadłem na to w scenariuszu, w którym zastosowano podejście top-n przy użyciu funkcji analitycznej.
Więc znalazłem funkcję
COLLECT
agregującą. Udokumentowano, że dostępny jest modyfikatorUNIQUE
lubDISTINCT
. Tylko w 10g cicho zawodzi (ignoruje modyfikator bez błędu). Aby jednak to przezwyciężyć, z innej odpowiedzi doszedłem do takiego 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 musiałbyś zdefiniować
tab_typ
jako podstawowy typ kolekcji, aw przypadku aVARCHAR
byłby to na przykład:CREATE OR REPLACE type tab_typ as table of varchar2(100) /
Również jako korekta odpowiedzi z @a_horse_with_no_name w sytuacji z wieloma kolumnami, w której możesz chcieć zagregować jeszcze trzecią (lub więcej) kolumnę:
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 zostawisz
rn = 1
warunek gdzie w zapytaniu, niepoprawnie agregujesz inne kolumny.źródło
Bardzo proste - użyj w swoim zapytaniu zapytania podrzędnego z wyraźnym zaznaczeniem:
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;
źródło
Najprostszym sposobem obsługi wielu listagg jest użycie 1 WITH (współczynnik podzapytania) na kolumnę zawierającą listagg tej kolumny z wybranego odrębnego:
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:
źródło