Farklı değerler döndürmek için Oracle'da LISTAGG


95

LISTAGGOracle'da işlevi kullanmaya çalışıyorum . O sütun için yalnızca farklı değerleri almak istiyorum. Bir fonksiyon veya prosedür oluşturmadan sadece farklı değerleri elde etmenin bir yolu var mı?

  col1 col2 Created_by
   1 2 Demirci 
   1 2 Yuhanna 
   1 3 Ajay 
   1 4 Koç 
   1 5 Jak 

Col1 ve LISTAGGcol2'yi seçmem gerekiyor (sütun 3 dikkate alınmaz). Bunu yaptığımda, şunun sonucunda şuna benzer bir şey elde ederim LISTAGG: [2,2,3,4,5]

Yinelenen '2'yi buradan kaldırmam gerekiyor; Col2'nin col1'e karşı yalnızca farklı değerlerine ihtiyacım var.



Örnekten beklenen çıkışı (satırları) gösterebilir misiniz? Col1 için birden fazla değer olup olmadığını görmek istersiniz?
a_horse_with_no_name

LISTAGG'nin beklenen çıktısı [2,3,4,5] 'dir. İkinci '2' kaldırılmalıdır. Ve masamda 1000'den fazla satır var.
Priyanth

Col1 için birden fazla değer olup olmadığını görmek istersiniz?
a_horse_with_no_name

Kod şu şekildedir: - grup içinde sütun1, LISTAGG (sütun2, ',') SEÇİN (sütun2'ye göre sırala) tablo T NEREDEN .... Bu nedenle, sütun1'e karşılık gelen sütun2'nin tüm farklı değerlerini şununla ayrılmış olarak gösterir: virgül.
Priyanth

Yanıtlar:


79

19c ve sonrası:

select listagg(distinct the_column, ',') within group (order by the_column)
from the_table

18c ve öncesi:

select listagg(the_column, ',') within group (order by the_column)
from (
   select distinct the_column 
   from the_table
) t

Daha fazla sütuna ihtiyacınız varsa, aradığınız şey şuna benzer bir şey olabilir:

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
Benim de aklımda olana benzer. listaggSorgudaki tek toplama işlevi buysa , bunun yapılması gerekir. Bununla birlikte, diğer toplama işlevleriyle birleştirmek daha zordur.
Andriy M

Evet. Sorgum buna benzer.
Priyanth

1
@a_horse_with_no_name: Yukarıdaki select deyimi benim için yinelenen değerler veriyor. Yinelenenleri kaldırmak istiyorum. col1 col2 Oluşturan: 1 2 Smith 1 2 John 1 3 Ajay 1 4 Ram 1 5 Jack col1 ve col2'nin LISTAGG'sini seçmem gerekiyor (sütun 3 dikkate alınmaz). Bunu yaparken LISTAGG: -> [2,2,3,4,5] burada yinelenen '2'yi kaldırmam gerekiyor. Col2'nin col1'e karşı yalnızca farklı değerlerine ihtiyacım var. .
Priyanth

@a_horse_with_no_name: Kodu denedim ve aşağıdaki gibi hata mesajını aldım ORA-01489: dizi birleştirme sonucu çok uzun 01489. 00000 - "dizi birleştirme sonucu çok uzun" * Neden: Dize birleştirme sonucu maksimumdan fazla boyut.
Priyanth

@ Priyanth: o zaman şansınız tükendi. Toplam uzunluk 4000 baytı aşıyor ve Oracle bunu kaldıramaz. Toplamayı uygulama kodunuzda yapmanız gerekecektir.
a_horse_with_no_name

48

Sorununuzu nasıl çözeceğiniz aşağıda açıklanmıştır.

select  
      regexp_replace(
    '2,2,2.1,3,3,3,3,4,4' 
     ,'([^,]+)(,\1)*(,|$)', '\1\3')

from dual

İadeler

2,2.1,3,4

Oracle 19C'den inşa edilmiştir buraya bakın

18C ve öncesi grup içinde deneyin buraya bakın

Aksi takdirde normal ifadeler kullanın

Aşağıda cevapla:

select col1, 

regexp_replace(
    listagg(
     col2 , ',') within group (order by col2)  -- sorted
    ,'([^,]+)(,\1)*(,|$)', '\1\3') )
   from tableX
where rn = 1
group by col1; 

Not: Yukarıdakiler çoğu durumda çalışacaktır - liste sıralanmalıdır, verilerinize bağlı olarak sondaki ve baştaki tüm boşlukları kırpmanız gerekebilir.

Bir grupta> 20 veya büyük dize boyutlarında çok sayıda öğeniz varsa, oracle dize boyutu sınırıyla karşılaşabilirsiniz 'dize birleştirme sonucu çok uzun'.

Oracle 12cR2'den bu hatayı bastırabilirsiniz buraya bakın . Alternatif olarak, her gruptaki üyelere bir maksimum sayı koyun. Bu sadece ilk üyelerin listelenmesi uygunsa işe yarar. Çok uzun değişken dizeleriniz varsa bu işe yaramayabilir. denemeniz gerekecek.

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;

(O kadar basit değil) Başka bir çözüm umarım oracle dize boyutu sınırını önlemek için - dize boyutu bu mesaja 4000 teşekkür sınırlıdır burada tarafından 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 - bazı test senaryoları - Bilginize

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-öğeler içinde bulunan öğeler örn. 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 - normal ifade Igor'a teşekkür edin! tüm durumlarda çalışır.

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
Makul sonuç, ancak o kadar basit değil. Ciddi veri boyutlarıyla karşılaşacaksınız ORA-01489: result of string concatenation is too long.
Pero

1
Ben buna basit ama çekici bir çözüm demezdim. Eşleşme numarasının yalnızca değiştirme dizesinde değil, arama dizesinde kullanılabileceğini bilmiyordum. Briliant.
Peter Krassoi

1
Bir uyarı olarak, bu yöntem, yinelenen değerlerin ardışık olması için değerlerin sıralanmasını gerektirir. Aksi takdirde başarısız olur. Ama basit iyidir! Ve bu yöntemi özel durumum için kullanıyorum. Teşekkürler!
StewS2

2
süper basit 3'ten fazla tekrarda işe yaramaz! , örneğin a,b,b,b,b,cşöyle olur a,b,b,c:-( (Oracle 11.2)
Andreas Dietrich

4
@AndreasDietrich - Aşağıdaki çözüm her zaman doğru görünüyor:regexp_replace(your_string, '([^,]+)(,\1)*(,|$)', '\1\3')
Egor Skriptunoff

10

belgelenmemiş wm_concatişlevi kullanabilirsiniz .

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

bu işlev clob sütununu döndürür, eğer isterseniz dbms_lob.substrclob'ı varchar2'ye dönüştürmek için kullanabilirsiniz.


16
Hayır, onu kullanma.
Koshinae

1
Bu tam olarak ihtiyacım olan şeydi ve bu sorguyu bir dış sorguya sarmak yerine mevcut toplu sorgumda mükemmel şekilde çalıştı. Kullanmanın nesi yanlış wm_concat(distinct x)?
Ehryk

1
çünkü belgelenmemiş ve 12c'de mevcut değil. ama yine de eski versiyonlarda bunun en iyi yol olduğunu düşünüyorum.
Kemalettin Erbakırcı

1
@ Kemalettinerbakırcı'ya teşekkürler! @thg Eğer bir şey belgelenmemişse, yan etkilerinin ne olduğunu bilmediğinizi ve Belgelerin size belgelenen işlevler hakkında söylediği diğer şeyleri bilmediğinizi dikkate almalısınız; onu sadece bir kara kutu olarak kullanırsınız ve sadece hangi kolun folklora dayalı olanı yaptığını bilirsiniz.
Koshinae


7

Önce değerleri gruplayarak, ardından listagg ile başka bir toplama yaparak bu sorunu aştım. Bunun gibi bir şey:

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)

yalnızca bir tam tablo erişimi, daha karmaşık sorgulara genişletmek nispeten kolaydır


6

Amaç bu dönüşümü birden çok sütuna uygulamaksa, a_horse_with_no_name çözümünü genişlettim:

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

Bu, Oracle Database 11g Enterprise Edition Sürüm 11.2.0.2.0 - 64bit Üretimdir.
STRAGG'yi kullanamadım çünkü DISTINCT ve SİPARİŞ etmenin bir yolu yok.

Performans doğrusal olarak ölçekleniyor, bu iyi, çünkü tüm ilgi sütunlarını ekliyorum. Yukarıdakiler 77K satır için 3 saniye sürdü. Yalnızca bir toplama için, 0,172 saniye. Tek geçişte bir tablodaki birden fazla sütunu ayırmanın bir yolu vardı.


6

MULTIPLE sütunlarda farklı değerler istiyorsanız, sıralama düzeni üzerinde kontrol istiyorsanız, kaybolabilecek belgelenmemiş bir işlevi kullanmak istemiyorsanız ve birden fazla tam tablo taraması istemiyorsanız, bu yapıyı yararlı bulabilirsiniz:

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
"Sendika" yı "tümünü birleştirme" ile değiştirirseniz biraz daha fazla zaman kazanabilirsiniz.
burkay

4

"Farklı" kısmı yapacak özel bir işlev oluşturmaya ne dersiniz:

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;
/

Ve sonra toplama yapmak için kullanın:

SELECT col1, listagg_distinct(cast(collect(col_2) as str_t ), ', ')
  FROM your_table
  GROUP BY col_1;

4

Dize uzunluğu sorununu aşmak XMLAGGiçin benzerini kullanabilirsiniz, listaggancak bir clob döndürür.

Daha sonra kullanarak ayrıştırabilir regexp_replaceve benzersiz değerleri alabilir ve ardından bunu kullanarak bir dizeye geri döndürebilirsiniz dbms_lob.substr(). Çok sayıda farklı değeriniz varsa, bu şekilde yine de alanınız tükenir, ancak çoğu durumda aşağıdaki kod çalışmalıdır.

Ayrıca kullandığınız sınırlayıcıları da değiştirebilirsiniz. Benim durumumda '' yerine '-' istedim, ancak kodumdaki çizgileri değiştirebilmeli ve isterseniz virgül kullanabilmelisiniz.

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

Bu, dönüşümleri kaldırmak ve & -> & amp için dbms_xmlgen.convert (string, 1) 'i çağırmak için harika bir fikirdir.
Gönderim

3

@ YoYo'nun @ a_horse_with_no_name'nin row_number () tabanlı yaklaşımına DECODE vs CASE ( burada gördüm ) kullanarak düzeltmesini daha da iyileştirme @Martin Vrbovsky'nin de bu vaka yaklaşımı cevabına sahip olduğunu görüyorum.

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

Destekleyecektir Yaklaşan Oracle 19c DISTINCTile LISTAGG.

DISTINCT seçeneği ile LISTAGG :

Bu özellik 19c ile geliyor:

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

DÜZENLE:

Oracle 19C LISTAGG DISTINCT

LISTAGG toplama işlevi artık yeni DISTINCT anahtar sözcüğünü kullanarak yinelenen eliminasyonu desteklemektedir. LISTAGG toplama işlevi, bir sorgudaki her grup için satırları ORDER BY ifadesine göre sıralar ve ardından değerleri tek bir dizede birleştirir. Yeni DISTINCT anahtar sözcüğüyle, tek bir dizeye birleştirilmeden önce belirtilen ifadeden yinelenen değerler kaldırılabilir. Bu, toplu LISTAGG işlevini kullanmadan önce farklı değerleri bulmak için karmaşık sorgu işlemi oluşturma ihtiyacını ortadan kaldırır. DISTINCT seçeneği ile, yinelenen değerleri kaldırmak için işleme doğrudan LISTAGG işlevi içinde yapılabilir. Sonuç daha basit, daha hızlı ve daha verimli SQL'dir.


1

listagg () NULL değerleri yok sayar, bu nedenle ilk adımda, önceki kaydın aynı değere sahip olup olmadığını analiz etmek için lag () işlevini kullanabilirsiniz, eğer öyleyse NULL, yoksa 'yeni değer'.

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;

Sonuçlar

      COL1 COL2_WITH_NULLS CREAT
---------- --------------- -----
         1               2 Smith
         1                 John
         1               3 Ajay
         1               4 Ram
         1               5 Jack

İkinci 2'nin NULL ile değiştirildiğine dikkat edin. Şimdi bir SELECT'i listagg () ile sarabilirsiniz.

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 );

Sonuç

COL2_LIST
---------
2,3,4,5

Bunu birden çok sütun üzerinden de yapabilirsiniz.

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 );

Sonuç

COL1_LIST COL2_LIST CREATED_BY_LIST
--------- --------- -------------------------
1         2,3,4,5   Ajay,Jack,John,Ram,Smith

0

PARTITION BY cümlesi kullanmayı düşünen var mı? Uygulama hizmetlerinin ve erişimin bir listesini almak için bu sorguda benim için çalıştı.

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 için nerede maddemi kesmek zorunda kaldım, ama sen anladın.


Bu sorgunun için nasıl farklı öğeler aldığını anlamıyorum LISTAGG. Görünüşe göre T.ACCESS_MODEgruplandırdığınız için satır başına yalnızca bir tane olacak mı?
jpmc26

0

Bunun yardımcı olabileceğini düşünüyorum - yineleniyorsa sütunların değerini NULL olarak CASE - o zaman LISTAGG dizesine eklenmez:

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   

Sonuçlar:

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

0

RegEx değiştirme yoluyla yapabilirsiniz. İşte bir örnek:

-- 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;

Ayrıca burada yayınlanmıştır: Oracle - benzersiz Listagg değerleri


0

Şu şekilde oluşturulmuş listagg_clob işlevini kullanın:

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

Bunu normal ifadeler kullanarak işlemek için bir işlev yazdım. İn parametreleri şunlardır: 1) listagg kendisini çağırır 2) Sınırlayıcının tekrarı

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;

Artık bunu her yaptığınızda normal ifadeyi tekrarlamanıza gerek yok, sadece şunu söyleyin:

select distinct_listagg(
                       listagg(myfield,', ') within group (order by 1),
                       ', '
                       )
     from mytable;

0

Birleştirilmiş değerler için belirli bir sıraya ihtiyacınız yoksa ve ayırıcı virgül olabilirse, şunları yapabilirsiniz:

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

0

Bunun DISTINCT versiyonunu belirledim ve bunu çalıştırdım.

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

0

Sinir bozucu bir yönü LISTAGG, birleştirilmiş dizenin toplam uzunluğu 4000 karakteri aşarsa ( VARCHAR2SQL'de sınır ) aşağıdaki hata atılır ve bu, 12.1'e kadar Oracle sürümlerinde yönetilmesi zordur.

ORA-01489: dize birleştirme sonucu çok uzun

12cR2 eklenen yeni bir özellik olduğu ON OVERFLOWbir fıkra LISTAGG. Bu maddeyi içeren sorgu şöyle görünecektir:

SELECT pid, LISTAGG(Desc, ' ' on overflow truncate) WITHIN GROUP (ORDER BY seq) AS desc
FROM B GROUP BY pid;

Yukarıdakiler, çıktıyı 4000 karakterle sınırlayacak ancak ORA-01489hatayı atmayacaktır.

Bunlar, ON OVERFLOWcümlenin ek seçeneklerinden bazılarıdır :

  • ON OVERFLOW TRUNCATE 'Contd..' : Bu 'Contd..', dizenin sonunda görüntülenecektir (Varsayılan değer ...)
  • ON OVERFLOW TRUNCATE '' : Bu, herhangi bir sonlandırma dizisi olmadan 4000 karakteri gösterecektir.
  • ON OVERFLOW TRUNCATE WITH COUNT: Bu, sonlandırılan karakterlerden sonra sondaki toplam karakter sayısını gösterecektir. Örneğin: - ' ...(5512)'
  • ON OVERFLOW ERROR: Hatanın LISTAGGbaşarısız olmasını bekliyorsanız ORA-01489(Bu varsayılan değerdir).

0

Bu depolanmış işlevi uyguladım:

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;

Üzgünüm ama bazı durumlarda (çok büyük bir set için) Oracle şu hatayı döndürebilir:

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.

ama bunun iyi bir başlangıç ​​noktası olduğunu düşünüyorum;)


0

select col1, listaggr(col2,',') within group(Order by col2) from table group by col1 Bu, dizeleri (col2) n sırasını koruyarak listeye toplayın ve daha sonra yinelenenleri col1'e göre grupla ele alın, yani col1 kopyalarını 1 grupta birleştirin. belki bu olması gerektiği kadar temiz ve basit görünüyor ve col3'ü de istiyorsanız, sadece bir tane daha listagg () eklemeniz gerekiyorselect col1, listaggr(col2,',') within group(Order by col2),listaggr(col3,',') within group(order by col3) from table group by col1


0

SELECT DISTINCT ...@A_horse_with_no_name tarafından belirtildiği gibi, LISTAGG'yi çağırmadan önce bir Alt Sorgunun bir parçası olarak kullanmak muhtemelen basit sorgular için en iyi yoldur

Ancak daha karmaşık sorgularda bunu başarmak mümkün veya kolay olmayabilir. Bunu, analitik bir fonksiyon kullanarak top-n yaklaşımını kullanan bir senaryoda ortaya çıkardım.

Böylece COLLECTtoplama işlevini buldum . UNIQUEVeya DISTINCTdeğiştiricinin mevcut olduğu belgelenmiştir . Yalnızca 10g'de sessizce başarısız olur (değiştiriciyi hatasız olarak yok sayar). Ancak bunun üstesinden gelmek için başka bir cevaptan şu çözüme geldim:

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,
    ...
)

Temel olarak, kullanarak SETkoleksiyonumdaki kopyaları kaldırırım.

Yine de tab_typtemel bir koleksiyon türü olarak tanımlamanız gerekir ve a olması durumunda VARCHAR, bu örneğin:

CREATE OR REPLACE type tab_typ as table of varchar2(100)
/

Ayrıca, @a_horse_with_no_name'den gelen yanıta, üçüncü (veya daha fazla) sütunda hala bir araya getirmek isteyebileceğiniz çok sütunlu durumda bir düzeltme olarak:

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 = 1Sorguya bir nerede koşulu olarak bırakırsanız, diğer sütunları yanlış bir şekilde toplarsınız.


0

Çok basit - sorgunuzda belirli bir alt sorguyu seçin:

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

Birden çok listagg'yi işlemenin en basit yolu, bir seçili farklı listeden o sütunun bir listesini içeren sütun başına 1 WITH (alt sorgu faktörü) kullanmaktır:

    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)

Hangi verir:

col1  col2List  col3List
1     2,3,4,5   3,4,6
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.