LISTAGG в Oracle для повернення різних значень


94

Я намагаюся використовувати LISTAGGфункцію в Oracle. Я хотів би отримати лише різні значення для цього стовпця. Чи є спосіб, за допомогою якого я можу отримати лише різні значення, не створюючи функції або процедури?

  col1 col2 Створено_
   1 2 Сміт 
   1 2 Івана 
   1 3 Аджай 
   1 4 Таран 
   1 5 Джек 

Мені потрібно вибрати col1 та the LISTAGGcol2 (стовпець 3 не враховується). Коли я це роблю, я отримую щось на зразок цього в результаті LISTAGG: [2,2,3,4,5]

Мені потрібно видалити дублікат "2" тут; Мені потрібні лише різні значення col2 проти col1.



Чи можете ви показати очікуваний вихід (рядки) із зразка? Що ви хочете побачити, якщо для col1 існує більше одного значення?
a_horse_with_no_name

Очікуваний результат LISTAGG становить [2,3,4,5]. Друге "2" слід видалити. І моя таблиця має більше 1000 рядків.
Приянт

Що ви хочете побачити, якщо для col1 існує більше одного значення?
a_horse_with_no_name

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

Відповіді:


77

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є єдиною сукупною функцією у запиті, це слід зробити. Однак поєднання його з іншими сукупними функціями є складнішим.
Андрій М

Так. Мій запит подібний до цього.
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 та СПИСОК col2 (стовпець 3 не враховується). Поки я роблю це, я отримаю щось подібне як результат LISTAGG: -> [2,2,3,4,5] Мені потрібно видалити дублікат'2 'тут. Мені потрібні лише різні значення col2 проти col1 .
Приянт

@a_horse_with_no_name: Я спробував код - і отримав повідомлення про помилку, як показано нижче ORA-01489: результат конкатенації рядків занадто довгий 01489. 00000 - "результат конкатенації рядків занадто довгий" * Причина: Результат конкатенації рядків перевищує максимальний розмір.
Приянт

@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 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. Завдяки цьому повідомленню тут по 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 - деякі тестові приклади - 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
Я б не назвав це простим, але дуже привабливим рішенням. Я не знав, що номер відповідності може бути використаний у пошуковому рядку не лише для рядка заміни. Блискуче.
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.


15
Ні, не користуйся цим.
Koshinae

1
Це було саме те, що мені потрібно, і чудово працювало в рамках мого існуючого зведеного запиту, замість того, щоб обертати цей запит у зовнішній. Що поганого у використанні wm_concat(distinct x)?
Ehryk

1
оскільки він не задокументований і не існує на 12в. але в будь-якому випадку на старих версіях я думаю, що це найкращий спосіб.
Kemalettin Erbakırcı

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, випуск 11.2.0.2.0 - 64-бітна версія.
Мені не вдалося скористатися STRAGG, оскільки немає способу ВИЗНАЧИТИ та ЗАМОВИТИ.

Ефективність масштабується лінійно, що добре, оскільки я додаю всі стовпці, що цікавлять. Вищезайняте 3 секунди для 77 тис. Рядків. Всього за один зведення, .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

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але вона повертає клоб.

Потім ви можете проаналізувати за допомогою 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 (рядок, 1), щоб видалити та & -> & перетворення. Дивіться моє посилання на
ozmike

3

Подальше вдосконалення корекції @ YoYo до підходу @ a_horse_with_no_name на основі row_number () з використанням 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

Майбутній 19с Oracle буде підтримувати 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

Мені довелося вирізати мій де-пункт для NDA, але ти розумієш.


Я не розумію, як цей запит бере різні елементи для LISTAGG. Здається, у вас був би лише один T.ACCESS_MODEна рядок, оскільки ви групуєтесь за ним?
jpmc26,

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 із listgg () навколо нього.

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

Я застосував DISTINCT версію цього і розробив цю.

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, вам просто потрібно додати ще один listgg (), тобто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сукупну функцію. Документована наявність наявного модифікатора 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як базовий тип колекції, а у випадку з 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запит як умову where, ви неправильно згрупуєте інші стовпці.


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

Найпростіший спосіб обробки декількох listgg - це використання 1 WITH (коефіцієнт підзапиту) на стовпець, що містить listgg цього стовпця з вибраного відмінного:

    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.