Я пытаюсь использовать LISTAGG
функцию в Oracle. Я хотел бы получить только отдельные значения для этого столбца. Есть ли способ получить только отдельные значения без создания функции или процедуры?
col1 col2 Created_by 1 2 Смит 1 2 Иоанна 1 3 Аджай 1 4 Баран 1 5 Джек
Мне нужно выбрать col1 и LISTAGG
col2 (столбец 3 не рассматривается). Когда я это делаю, я получаю что-то вроде этого в результате LISTAGG
: [2,2,3,4,5]
Мне нужно удалить здесь дубликат «2»; Мне нужны только различные значения col2 против col1.
sql
oracle
aggregate-functions
listagg
Приянт
источник
источник
Ответы:
19c и позже:
select listagg(distinct the_column, ',') within group (order by the_column) from the_table
18c и ранее:
select listagg(the_column, ',') within group (order by the_column) from ( select distinct the_column from the_table ) t
Если вам нужно больше столбцов, возможно, вы ищете что-то вроде этого:
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;
источник
listagg
это единственная агрегатная функция в запросе, это должно быть сделано. Однако объединить его с другими агрегатными функциями сложнее.Вот как решить вашу проблему.
select regexp_replace( '2,2,2.1,3,3,3,3,4,4' ,'([^,]+)(,\1)*(,|$)', '\1\3') from dual
возвращается
2,2.1,3,4
От oracle 19C он встроен см. Здесь
Начиная с 18C, попробуйте в группе см. Здесь
В противном случае используйте регулярные выражения
ОТВЕТ ниже:
select col1, regexp_replace( listagg( col2 , ',') within group (order by col2) -- sorted ,'([^,]+)(,\1)*(,|$)', '\1\3') ) from tableX where rn = 1 group by col1;
Примечание. Вышеупомянутое будет работать в большинстве случаев - список должен быть отсортирован, вам может потребоваться обрезать все конечные и ведущие пробелы в зависимости от ваших данных.
Если у вас много элементов в группе> 20 или большие размеры строк, вы можете столкнуться с ограничением размера строки Oracle «результат конкатенации строк слишком длинный».
Из oracle 12cR2 вы можете подавить эту ошибку, см. Здесь . В качестве альтернативы укажите максимальное количество членов в каждой группе. Это будет работать только в том случае, если можно указать только первых участников. Если у вас очень длинные переменные строки, это может не сработать. вам придется поэкспериментировать.
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;
Другое решение (не такое простое), чтобы, надеюсь, избежать ограничения размера строки оракула - размер строки ограничен 4000. Благодаря этому сообщению здесь пользователем 3465996
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 - некоторые тестовые примеры - FYI
V2 -элементы, содержащиеся в элементах, например. 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 - регулярное выражение спасибо Игорю! работает во всех случаях.
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
источник
ORA-01489: result of string concatenation is too long
.a,b,b,b,b,c
, станетa,b,b,c
:-( (Oracle 11.2)regexp_replace(your_string, '([^,]+)(,\1)*(,|$)', '\1\3')
вы можете использовать недокументированную
wm_concat
функцию.select col1, wm_concat(distinct col2) col2_list from tab1 group by col1;
эта функция возвращает столбец clob, если вы хотите, вы можете использовать его
dbms_lob.substr
для преобразования clob в varchar2.источник
wm_concat(distinct x)
?wm_concat
. См. Почему бы не использовать функцию WM_CONCAT в Oracle? .Я решил эту проблему, сначала сгруппировав значения, а затем сделав еще одну агрегацию с помощью listagg. Что-то вроде этого:
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)
только один полный доступ к таблице, относительно легко расширяемый для более сложных запросов
источник
Если намерение состоит в том, чтобы применить это преобразование к нескольким столбцам, я расширил решение 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
Это Oracle Database 11g Enterprise Edition Release 11.2.0.2.0 - 64-разрядная производственная версия.
Мне не удалось использовать STRAGG, потому что нет возможности ОТЛИЧИТЬ и ЗАКАЗАТЬ.
Производительность масштабируется линейно, что хорошо, поскольку я добавляю все интересующие столбцы. Вышеупомянутое заняло 3 секунды для 77K строк. Всего за одно сведение - 0,172 секунды. У меня был способ различать несколько столбцов в таблице за один проход.
источник
Если вам нужны разные значения в НЕСКОЛЬКИХ столбцах, вам нужен контроль над порядком сортировки, вы не хотите использовать недокументированную функцию, которая может исчезнуть, и не хотите более одного полного сканирования таблицы, вы можете найти эту конструкцию полезной:
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 );
источник
А как насчет создания специальной функции, которая будет составлять «отличную» часть:
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; /
А затем используйте его для агрегирования:
SELECT col1, listagg_distinct(cast(collect(col_2) as str_t ), ', ') FROM your_table GROUP BY col_1;
источник
Чтобы обойти проблему с длиной строки, вы можете использовать
XMLAGG
ее аналог,listagg
но она возвращает clob.Затем вы можете проанализировать с помощью
regexp_replace
и получить уникальные значения, а затем снова превратить их в строку с помощьюdbms_lob.substr()
. Если у вас есть огромное количество различных значений, вам все равно не хватит места, но для многих случаев приведенный ниже код должен работать.Вы также можете изменить используемые разделители. В моем случае я хотел '-' вместо ',' но вы должны иметь возможность заменить дефисы в моем коде и использовать запятые, если хотите.
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
источник
Дальнейшее уточнение поправки @YoYo к подходу на основе row_number () @ a_horse_with_no_name с использованием DECODE vs CASE ( я видел здесь ). Я вижу, что у @Martin Vrbovsky также есть ответ на этот случай.
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;
источник
Предстоящий Oracle 19c будет поддерживать
DISTINCT
сLISTAGG
.РЕДАКТИРОВАТЬ:
Oracle 19C LISTAGG DISTINCT
источник
Кто-нибудь думал об использовании предложения PARTITION BY? В этом запросе у меня сработало получение списка служб приложений и доступа.
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
Мне пришлось исключить пункт where для NDA, но вы поняли идею.
источник
LISTAGG
. Похоже, что у вас будет только одинT.ACCESS_MODE
на строку, так как вы группируете по ней?Я думаю, это может помочь - ЗАНИМАЙТЕ значение столбца NULL, если оно дублируется - тогда оно не добавляется к строке 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
Результаты в:
источник
listagg () игнорирует значения NULL, поэтому на первом этапе вы можете использовать функцию lag (), чтобы проанализировать, имеет ли предыдущая запись то же значение, если да, то NULL, иначе - «новое значение».
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;
Полученные результаты
COL1 COL2_WITH_NULLS CREAT ---------- --------------- ----- 1 2 Smith 1 John 1 3 Ajay 1 4 Ram 1 5 Jack
Обратите внимание, что вторая 2 заменена на NULL. Теперь вы можете обернуть SELECT с помощью 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 );
Результат
COL2_LIST --------- 2,3,4,5
Вы также можете сделать это для нескольких столбцов.
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 );
Результат
COL1_LIST COL2_LIST CREATED_BY_LIST --------- --------- ------------------------- 1 2,3,4,5 Ajay,Jack,John,Ram,Smith
источник
Вы можете сделать это с помощью замены RegEx. Вот пример:
-- 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;
Также размещено здесь: Oracle - уникальные значения Listagg
источник
Используйте функцию 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
источник
Я написал функцию для обработки этого, используя регулярные выражения. Параметры in: 1) listagg вызывает сам себя 2) повторение разделителя
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;
Теперь вам не нужно каждый раз повторять регулярное выражение, просто скажите:
select distinct_listagg( listagg(myfield,', ') within group (order by 1), ', ' ) from mytable;
источник
Если вам не нужен определенный порядок конкатенированных значений, а разделителем может быть запятая, вы можете сделать:
select col1, stragg(distinct col2) from table group by col1
источник
Мне нужна была ОТЛИЧНАЯ версия этого, и эта работала.
источник
Один раздражающий аспект
LISTAGG
заключается в том, что если общая длина объединенной строки превышает 4000 символов (ограничение дляVARCHAR2
SQL), возникает ошибка, указанная ниже, с которой трудно справиться в версиях Oracle до 12.1.Новая функция, добавленная в 12cR2, - это
ON OVERFLOW
пунктLISTAGG
. Запрос, включающий это предложение, будет выглядеть так:SELECT pid, LISTAGG(Desc, ' ' on overflow truncate) WITHIN GROUP (ORDER BY seq) AS desc FROM B GROUP BY pid;
Вышеуказанное ограничит вывод до 4000 символов, но не вызовет
ORA-01489
ошибку.Вот некоторые из дополнительных вариантов
ON OVERFLOW
статьи:ON OVERFLOW TRUNCATE 'Contd..'
: Это будет отображаться'Contd..'
в конце строки (по умолчанию...
)ON OVERFLOW TRUNCATE ''
: Это отобразит 4000 символов без какой-либо завершающей строки.ON OVERFLOW TRUNCATE WITH COUNT
: Это отобразит общее количество символов в конце после завершающих символов. Например: - '...(5512)
'ON OVERFLOW ERROR
: Если вы ожидаете, чтоLISTAGG
произойдет сбой сORA-01489
ошибкой (что в любом случае по умолчанию).источник
Я реализовал эту сохраненную функцию:
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;
Извините, но в некоторых случаях (для очень большого набора) Oracle может вернуть такую ошибку:
но я думаю, это хорошая отправная точка;)
источник
select col1, listaggr(col2,',') within group(Order by col2) from table group by col1
что означает объединение строк (col2) в список, сохраняя порядок n, а затем обрабатывать дубликаты как группу по col1, что означает объединение дубликатов col1 в 1 группу. возможно, это выглядит чисто и просто, как и должно быть, и если вам нужен col3, вам просто нужно добавить еще один listagg (), которыйselect col1, listaggr(col2,',') within group(Order by col2),listaggr(col3,',') within group(order by col3) from table group by col1
источник
Использование в
SELECT DISTINCT ...
качестве части подзапроса перед вызовом LISTAGG, вероятно, лучший способ для простых запросов, как отмечает @a_horse_with_no_nameОднако в более сложных запросах это может быть невозможно или просто сделать. У меня это возникло в сценарии, в котором использовался подход top-n с использованием аналитической функции.
Итак, я нашел
COLLECT
агрегатную функцию. Документально подтверждено наличиеUNIQUE
DISTINCT
модификатора или . Только в 10g тихо выходит из строя (игнорирует модификатор без ошибок). Однако, чтобы преодолеть это, из другого ответа я пришел к этому решению: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, ... )
В основном, используя
SET
, я удаляю дубликаты в своей коллекции.Вам все равно нужно будет определить
tab_typ
как базовый тип коллекции, и в случае aVARCHAR
это будет, например:CREATE OR REPLACE type tab_typ as table of varchar2(100) /
Также в качестве исправления ответа от @a_horse_with_no_name в ситуации с несколькими столбцами, когда вы можете захотеть агрегировать еще по третьему (или более) столбцам:
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;
Если вы оставите в
rn = 1
качестве условия для запроса, вы неправильно агрегируете другие столбцы.источник
Очень просто - используйте в своем запросе подзапрос с отдельным выбором:
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;
источник
Самый простой способ обрабатывать несколько listagg - использовать 1 WITH (фактор подзапроса) для каждого столбца, содержащего listagg этого столбца из выбранного объекта:
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)
Который дает:
источник