LISTAGG в Oracle для возврата различных значений


95

Я пытаюсь использовать LISTAGGфункцию в Oracle. Я хотел бы получить только отдельные значения для этого столбца. Есть ли способ получить только отдельные значения без создания функции или процедуры?

  col1 col2 Created_by
   1 2 Смит 
   1 2 Иоанна 
   1 3 Аджай 
   1 4 Баран 
   1 5 Джек 

Мне нужно выбрать col1 и LISTAGGcol2 (столбец 3 не рассматривается). Когда я это делаю, я получаю что-то вроде этого в результате LISTAGG: [2,2,3,4,5]

Мне нужно удалить здесь дубликат «2»; Мне нужны только различные значения col2 против col1.



Можете ли вы показать ожидаемые результаты (строки) из образца? Что вы хотите увидеть, если для col1 существует более одного значения?
a_horse_with_no_name

Ожидаемый результат LISTAGG - [2,3,4,5]. Вторую цифру «2» следует удалить. А в моей таблице более 1000 строк.
Priyanth

Что вы хотите видеть, если для col1 существует более одного значения?
a_horse_with_no_name

Код выглядит следующим образом: - ВЫБЕРИТЕ col1, LISTAGG (col2, ',') внутри группы (порядок по col2) ИЗ таблицы T WHERE .... Итак, он должен показать все различные значения col2, соответствующие col1, разделенные запятая.
Priyanth

Ответы:


78

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;

2
Подобно тому, что я имел в виду. Если listaggэто единственная агрегатная функция в запросе, это должно быть сделано. Однако объединить его с другими агрегатными функциями сложнее.
Andriy M

Да. Мой запрос похож на этот.
Priyanth

1
@a_horse_with_no_name: приведенный выше оператор select дает мне повторяющиеся значения. Я хочу удалить дубликаты. col1 col2 Создано 1 2 Smith 1 2 John 1 3 Ajay 1 4 Ram 1 5 Jack Мне нужно выбрать col1 и LISTAGG col2 (столбец 3 не рассматривается). Пока я это сделаю, я получу что-то вроде этого в результате LISTAGG: -> [2,2,3,4,5] Мне нужно удалить здесь дубликат '2'. Мне нужны только отдельные значения col2 против col1 .
Priyanth

@a_horse_with_no_name: я попробовал код - и получил сообщение об ошибке, как показано ниже ORA-01489: результат конкатенации строк слишком длинный 01489. 00000 - «результат конкатенации строк слишком длинный» * Причина: результат конкатенации строк больше максимального размер.
Priyanth

@Priyanth: значит тебе не повезло. Общая длина превышает 4000 байт, и Oracle не может справиться с этим. Вам нужно будет выполнить агрегирование в коде вашего приложения.
a_horse_with_no_name

47

Вот как решить вашу проблему.

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

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 -элементы, содержащиеся в элементах, например. 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

3
Хороший результат, но не все так просто. Вы столкнетесь с серьезными объемами данных ORA-01489: result of string concatenation is too long.
Pero

1
Я бы не назвал это простым, но очень привлекательным решением. Я не знал, что номер совпадения можно использовать в строке поиска, а не только в строке замены. Briliant.
Peter Krassoi

1
В качестве предостережения, этот метод требует, чтобы значения были отсортированы, чтобы повторяющиеся значения были последовательными. Иначе не получится. Но простое - это хорошо! И я использую этот метод для своего конкретного случая. Благодарность!
StewS2

2
супер просто не получается больше 3-х повторов! , например a,b,b,b,b,c, станет a,b,b,c:-( (Oracle 11.2)
Андреас Дитрих

4
@AndreasDietrich - Следующее решение кажется всегда правильным:regexp_replace(your_string, '([^,]+)(,\1)*(,|$)', '\1\3')
Егор Скриптунов

10

вы можете использовать недокументированную wm_concatфункцию.

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

эта функция возвращает столбец clob, если вы хотите, вы можете использовать его dbms_lob.substrдля преобразования clob в varchar2.


16
Нет, не используйте это.
Кошинаэ

1
Это было именно то, что мне было нужно, и оно отлично работало в рамках моего существующего агрегированного запроса вместо того, чтобы заключать этот запрос во внешний. Что не так с использованием wm_concat(distinct x)?
Эрик

1
потому что это не задокументировано и не существует на 12c. но в любом случае на старых версиях я думаю, что это лучший способ.
Кемалеттин Эрбакырджы

1
Спасибо @ kemalettinerbakırcı! @thg вам следует учитывать, что если что-то недокументировано, вы не знаете, каковы его побочные эффекты, и любые другие вещи, которые Документация сообщает вам о документированных функциях; вы просто используете его как черный ящик, и вы знаете только, какой рычаг что делает, основываясь на фольклоре.
Koshinae

4
Никогда не используйте wm_concat. См. Почему бы не использовать функцию WM_CONCAT в Oracle? .
Lalit Kumar B

7

Я решил эту проблему, сначала сгруппировав значения, а затем сделав еще одну агрегацию с помощью 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)

только один полный доступ к таблице, относительно легко расширяемый для более сложных запросов


6

Если намерение состоит в том, чтобы применить это преобразование к нескольким столбцам, я расширил решение 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 секунды. У меня был способ различать несколько столбцов в таблице за один проход.


6

Если вам нужны разные значения в НЕСКОЛЬКИХ столбцах, вам нужен контроль над порядком сортировки, вы не хотите использовать недокументированную функцию, которая может исчезнуть, и не хотите более одного полного сканирования таблицы, вы можете найти эту конструкцию полезной:

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
Вы можете сэкономить немного времени, если замените "union" на "union all".
Burkay 06

4

А как насчет создания специальной функции, которая будет составлять «отличную» часть:

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;

4

Чтобы обойти проблему с длиной строки, вы можете использовать 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

Это отличная идея - вызвать dbms_xmlgen.convert (string, 1) для удаления и & -> & amp преобразования. См. Ссылку на
ozmike

3

Дальнейшее уточнение поправки @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;

2

Предстоящий Oracle 19c будет поддерживать DISTINCTс LISTAGG.

LISTAGG с опцией DISTINCT :

Эта функция поставляется с 19c:

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

РЕДАКТИРОВАТЬ:

Oracle 19C LISTAGG DISTINCT

Агрегатная функция LISTAGG теперь поддерживает устранение дубликатов с помощью нового ключевого слова DISTINCT. Агрегатная функция LISTAGG упорядочивает строки для каждой группы в запросе в соответствии с выражением ORDER BY, а затем объединяет значения в одну строку. С помощью нового ключевого слова DISTINCT можно удалить повторяющиеся значения из указанного выражения перед объединением в одну строку. Это избавляет от необходимости создавать сложную обработку запросов для поиска отдельных значений перед использованием агрегированной функции LISTAGG. С помощью опции DISTINCT обработка для удаления повторяющихся значений может выполняться непосредственно в функции LISTAGG. Результат - более простой, быстрый и эффективный SQL.


0

Кто-нибудь думал об использовании предложения 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на строку, так как вы группируете по ней?
jpmc26 04

0

Я думаю, это может помочь - ЗАНИМАЙТЕ значение столбца 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   

Результаты в:

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

0

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

0

Вы можете сделать это с помощью замены 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


0

Используйте функцию 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

Я написал функцию для обработки этого, используя регулярные выражения. Параметры 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;

0

Если вам не нужен определенный порядок конкатенированных значений, а разделителем может быть запятая, вы можете сделать:

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

0

Мне нужна была ОТЛИЧНАЯ версия этого, и эта работала.

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

0

Один раздражающий аспект LISTAGGзаключается в том, что если общая длина объединенной строки превышает 4000 символов (ограничение для VARCHAR2SQL), возникает ошибка, указанная ниже, с которой трудно справиться в версиях Oracle до 12.1.

ORA-01489: результат конкатенации строк слишком длинный

Новая функция, добавленная в 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ошибкой (что в любом случае по умолчанию).

0

Я реализовал эту сохраненную функцию:

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 может вернуть такую ​​ошибку:

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.

но я думаю, это хорошая отправная точка;)


0

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


0

Использование в SELECT DISTINCT ...качестве части подзапроса перед вызовом LISTAGG, вероятно, лучший способ для простых запросов, как отмечает @a_horse_with_no_name

Однако в более сложных запросах это может быть невозможно или просто сделать. У меня это возникло в сценарии, в котором использовался подход top-n с использованием аналитической функции.

Итак, я нашел COLLECTагрегатную функцию. Документально подтверждено наличиеUNIQUEDISTINCT модификатора или . Только в 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как базовый тип коллекции, и в случае a VARCHARэто будет, например:

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качестве условия для запроса, вы неправильно агрегируете другие столбцы.


0

Очень просто - используйте в своем запросе подзапрос с отдельным выбором:

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

Самый простой способ обрабатывать несколько 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)

Который дает:

col1  col2List  col3List
1     2,3,4,5   3,4,6
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.