LISTAGG ใน Oracle เพื่อส่งคืนค่าที่แตกต่างกัน


95

ฉันพยายามใช้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



คุณสามารถแสดง ouptut (แถว) ที่คาดไว้จากตัวอย่างได้หรือไม่ คุณต้องการดูอะไรว่ามีค่ามากกว่าหนึ่งค่าสำหรับ col1 หรือไม่
a_horse_with_no_name

ผลลัพธ์ที่คาดหวังของ LISTAGG คือ [2,3,4,5] ควรลบ "2" ที่สองออก และตารางของฉันมีมากกว่า 1,000 แถว
Priyanth

คุณต้องการดูอะไรว่ามีค่ามากกว่าหนึ่งค่าสำหรับ col1 หรือไม่
a_horse_with_no_name

รหัสเป็นดังนี้ - SELECT col1, LISTAGG (col2, ',') ภายในกลุ่ม (เรียงลำดับโดย col2) จากตาราง T WHERE .... ดังนั้นมันจึงแสดงค่าที่แตกต่างกันทั้งหมดของ col2 ที่สอดคล้องกับ col1 โดยแยกตาม ลูกน้ำ
Priyanth

คำตอบ:


79

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

48

วิธีแก้ปัญหาของคุณมีดังนี้

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;

อีกวิธีหนึ่ง (ไม่ง่ายนัก) เพื่อหลีกเลี่ยงการ จำกัด ขนาดสตริง oracle - ขนาดสตริง จำกัด ไว้ที่ 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 - regex ขอบคุณอิกอร์! ใช้ได้กับทุกกรณี

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
super simple ใช้ไม่ได้กับการทำซ้ำมากกว่า 3 ครั้ง! เช่นa,b,b,b,b,cจะกลายเป็นa,b,b,c:-( (Oracle 11.2)
Andreas Dietrich

4
@AndreasDietrich - วิธีแก้ปัญหาต่อไปนี้ดูเหมือนจะถูกต้องเสมอ:regexp_replace(your_string, '([^,]+)(,\1)*(,|$)', '\1\3')
Egor Skriptunoff

10

คุณสามารถใช้wm_concatฟังก์ชันที่ไม่มีเอกสาร

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

ฟังก์ชันนี้ส่งคืนคอลัมน์ clob หากคุณต้องการคุณสามารถใช้dbms_lob.substrเพื่อแปลง clob เป็น varchar2


16
ไม่อย่าใช้สิ่งนั้น
Koshinae

1
นี่คือสิ่งที่ฉันต้องการอย่างแท้จริงและทำงานได้อย่างสมบูรณ์แบบภายในการสืบค้นข้อมูลรวมที่มีอยู่ของฉันแทนที่จะห่อแบบสอบถามนั้นไว้ในแบบสอบถามภายนอก ใช้อะไรผิดwm_concat(distinct x)?
Ehryk

1
เนื่องจากไม่มีเอกสารและไม่มีอยู่ใน 12c แต่อย่างไรก็ตามในเวอร์ชันเก่าฉันคิดว่าเป็นวิธีที่ดีที่สุด
Kemalettin Erbakırcı

1
ขอบคุณ @ kemalettinerbakırcı! @thg คุณควรพิจารณาว่าหากมีบางสิ่งที่ไม่มีเอกสารคุณไม่รู้ว่ามันคือผลข้างเคียงอะไรและสิ่งอื่น ๆ ที่เอกสารนี้จะบอกคุณเกี่ยวกับฟังก์ชันที่บันทึกไว้ คุณแค่ใช้มันเป็นกล่องดำและคุณรู้แค่ว่าคันไหนทำอะไรตามคติชนวิทยา
Koshinae


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 - 64bit Production
ฉันไม่สามารถใช้ STRAGG ได้เนื่องจากไม่มีวิธี DISTINCT และ ORDER

ประสิทธิภาพสเกลเชิงเส้นซึ่งเป็นสิ่งที่ดีเนื่องจากฉันกำลังเพิ่มคอลัมน์ที่น่าสนใจทั้งหมด ข้างต้นใช้เวลา 3 วินาทีสำหรับ 77K แถว เพียงครั้งเดียว Rollup, .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แต่จะส่งคืน 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; 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 ง่ายขึ้นเร็วขึ้นและมีประสิทธิภาพมากขึ้น


1

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

มีใครคิดจะใช้ PARTITION BY clause บ้าง? มันได้ผลสำหรับฉันในแบบสอบถามนี้เพื่อรับรายการบริการแอปพลิเคชันและการเข้าถึง

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

คุณสามารถทำได้โดยการเปลี่ยน 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

ฉันเขียนฟังก์ชันเพื่อจัดการสิ่งนี้โดยใช้นิพจน์ทั่วไป พารามิเตอร์ในคือ 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ฟังก์ชันการรวม มีการบันทึกว่ามี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เป็นเงื่อนไขที่ในการสืบค้นคุณจะรวมคอลัมน์อื่น ๆ ไม่ถูกต้อง


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 C (ปัจจัยการสืบค้นย่อย) ต่อคอลัมน์ที่มี 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.