LISTAGG dans Oracle pour renvoyer des valeurs distinctes


94

J'essaye d'utiliser la LISTAGGfonction dans Oracle. Je voudrais obtenir uniquement les valeurs distinctes pour cette colonne. Existe-t-il un moyen d'obtenir uniquement les valeurs distinctes sans créer de fonction ou de procédure?

  col1 col2 Created_by
   1 2 Smith 
   1 2 Jean 
   1 3 Ajay 
   1 4 Bélier 
   1 5 Jack 

Je dois sélectionner col1 et le LISTAGGde col2 (la colonne 3 n'est pas prise en compte). Quand je fais cela, j'obtiens quelque chose comme ça à la suite de LISTAGG: [2,2,3,4,5]

Je dois supprimer le duplicata «2» ici; Je n'ai besoin que des valeurs distinctes de col2 par rapport à col1.



Pouvez-vous montrer la sortie attendue (lignes) de l'échantillon? Que voulez-vous voir s'il y a plus d'une valeur pour col1?
a_horse_with_no_name

Le résultat attendu du LISTAGG est [2,3,4,5]. Le deuxième «2» doit être supprimé. Et ma table a plus de 1000 lignes.
Priyanth

Que voulez-vous voir s'il y a plus d'une valeur pour col1?
a_horse_with_no_name

Le code est comme ceci: - SELECT col1, LISTAGG (col2, ',') within group (order by col2) FROM table T WHERE .... Donc, il devrait montrer toutes les valeurs distinctes de col2 correspondant à col1, séparées par virgule.
Priyanth

Réponses:


77

19c et plus tard:

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

18c et plus tôt:

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

Si vous avez besoin de plus de colonnes, quelque chose comme celui-ci pourrait être ce que vous recherchez:

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
Similaire à ce que j'avais en tête aussi. Si listaggest la seule fonction d'agrégation dans la requête, cela devrait faire. Le combiner avec d'autres fonctions d'agrégation, cependant, est plus délicat.
Andriy M

Oui. Ma requête est similaire à celle-ci.
Priyanth

1
@a_horse_with_no_name: L'instruction select ci-dessus donne des valeurs en double pour moi. Je souhaite supprimer les doublons. col1 col2 Créé par 1 2 Smith 1 2 John 1 3 Ajay 1 4 Ram 1 5 Jack Je dois sélectionner col1 et le LISTAGG de col2 (la colonne 3 n'est pas prise en compte). Pendant que je fais cela, j'obtiendrai quelque chose comme ça comme résultat od LISTAGG: -> [2,2,3,4,5] Je dois supprimer le duplicate'2 'ici.Je n'ai besoin que des valeurs distinctes de col2 contre col1 .
Priyanth

@a_horse_with_no_name: j'ai essayé le code et j'ai reçu le message d'erreur ci-dessous ORA-01489: le résultat de la concaténation de chaînes est trop long 01489. 00000 - "le résultat de la concaténation de chaînes est trop long" * Cause: le résultat de la concaténation de chaînes est supérieur au maximum Taille.
Priyanth

@Priyanth: alors vous n'avez pas de chance. La longueur totale dépasse 4 000 octets et Oracle ne peut pas gérer cela. Vous devrez effectuer l'agrégation dans votre code d'application.
a_horse_with_no_name

47

Voici comment résoudre votre problème.

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

from dual

Retour

2,2.1,3,4

Depuis l'oracle 19C, il est intégré voir ici

À partir de 18 ° C et avant, essayez en groupe voir ici

Sinon, utilisez des expressions régulières

Réponse ci-dessous:

select col1, 

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

Remarque: ce qui précède fonctionnera dans la plupart des cas - la liste doit être triée, vous devrez peut-être couper tous les espaces de fin et de début en fonction de vos données.

Si vous avez un grand nombre d'éléments dans un groupe> 20 ou de grandes tailles de chaîne, vous risquez de rencontrer une limite de taille de chaîne oracle 'le résultat de la concaténation de chaînes est trop long'.

Depuis oracle 12cR2, vous pouvez supprimer cette erreur, voir ici . Vous pouvez également mettre un nombre maximum de membres dans chaque groupe. Cela ne fonctionnera que si vous pouvez lister uniquement les premiers membres. Si vous avez de très longues chaînes de variables, cela peut ne pas fonctionner. vous devrez expérimenter.

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;

Une autre solution (pas si simple) pour éviter si tout va bien la limite de taille de chaîne d'oracle - la taille de chaîne est limitée à 4000. Merci à ce post ici par 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 - quelques cas de test - 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 - éléments contenus dans les éléments, par exemple. 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 merci Igor! fonctionne tous les cas.

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
Résultat juste, mais pas si simple. Vous rencontrerez des tailles de données importantes ORA-01489: result of string concatenation is too long.
Pero

1
Je n'appellerais pas cela une solution simple mais très attrayante. Je ne savais pas que le numéro de correspondance pouvait être utilisé dans la chaîne de recherche non seulement dans la chaîne de remplacement. Brillant.
Peter Krassoi

1
En guise de mise en garde, cette méthode nécessite que les valeurs soient triées, de sorte que les valeurs dupliquées soient consécutives. Sinon, cela échoue. Mais simple c'est bien! Et j'utilise cette méthode pour mon cas particulier. Merci!
StewS2

2
super simple ne fonctionne pas plus de 3 répétitions! , par exemple a,b,b,b,b,cdeviendrait a,b,b,c:-( (Oracle 11.2)
Andreas Dietrich

4
@AndreasDietrich - La solution suivante semble toujours correcte:regexp_replace(your_string, '([^,]+)(,\1)*(,|$)', '\1\3')
Egor Skriptunoff

10

vous pouvez utiliser la wm_concatfonction non documentée .

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

cette fonction renvoie la colonne clob, si vous le souhaitez, vous pouvez l'utiliser dbms_lob.substrpour convertir clob en varchar2.


15
Non, n'utilisez pas ça.
Koshinae

1
C'était exactement ce dont j'avais besoin et cela fonctionnait parfaitement dans ma requête agrégée existante au lieu d'encapsuler cette requête dans une requête externe. Quel est le problème avec l'utilisation wm_concat(distinct x)?
Ehryk le

1
car il n'est pas documenté et n'existe pas sur 12c. mais de toute façon sur les anciennes versions je pense que c'est le meilleur moyen.
Kemalettin Erbakırcı

1
Merci @ kemalettinerbakırcı! @thg vous devriez considérer que si quelque chose n'est pas documenté, vous ne savez pas quels sont ses effets secondaires, et tout autre genre de choses que la Documentation vous dit sur les fonctions documentées; vous l'utilisez simplement comme une boîte noire et vous ne savez quel levier fait quoi basé sur le folklore.
Koshinae


7

J'ai surmonté ce problème en regroupant d'abord les valeurs, puis en effectuant une autre agrégation avec le listagg. Quelque chose comme ça:

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)

un seul accès complet à la table, relativement facile à étendre à des requêtes plus complexes


6

Si l'intention est d'appliquer cette transformation à plusieurs colonnes, j'ai étendu la solution de 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

Il s'agit d'Oracle Database 11g Enterprise Edition Release 11.2.0.2.0 - Production 64 bits.
Je n'ai pas pu utiliser STRAGG car il n'y a aucun moyen de DISTINCT et de COMMANDER.

Les performances évoluent de manière linéaire, ce qui est bien, car j'ajoute toutes les colonnes d'intérêt. Ce qui précède a pris 3 secondes pour 77K lignes. Pour un seul cumul, 0,172 secondes. Je fais avec il y avait un moyen de distinguer plusieurs colonnes dans une table en un seul passage.


6

Si vous voulez des valeurs distinctes sur plusieurs colonnes, que vous voulez contrôler l'ordre de tri, que vous ne voulez pas utiliser une fonction non documentée qui peut disparaître et que vous ne voulez pas plus d'une analyse complète de la table, vous pouvez trouver cette construction utile:

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
Vous pouvez gagner plus de temps si vous remplacez "union" s par "union all".
burkay

4

Qu'en est-il de la création d'une fonction dédiée qui fera la partie "distincte":

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

Et puis utilisez-le pour faire l'agrégation:

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

4

Pour contourner le problème de longueur de chaîne, vous pouvez utiliser XMLAGGce qui est similaire à listaggmais cela renvoie un clob.

Vous pouvez ensuite analyser en utilisant regexp_replaceet obtenir les valeurs uniques, puis les reconvertir en chaîne en utilisant dbms_lob.substr(). Si vous avez une énorme quantité de valeurs distinctes, vous manquerez toujours d'espace de cette façon, mais dans de nombreux cas, le code ci-dessous devrait fonctionner.

Vous pouvez également modifier les délimiteurs que vous utilisez. Dans mon cas, je voulais "-" au lieu de "," mais vous devriez pouvoir remplacer les tirets dans mon code et utiliser des virgules si vous le souhaitez.

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

C'est une excellente idée d'appeler dbms_xmlgen.convert (string, 1) pour supprimer les conversions et & -> & amp. Voir mon lien de
ozmike

3

Amélioration de la correction de @ YoYo à l'approche basée sur row_number () de @ a_horse_with_no_name en utilisant DECODE vs CASE ( j'ai vu ici ). Je vois que @Martin Vrbovsky a également cette réponse d'approche de cas.

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 à venir prendra en charge DISTINCTavec LISTAGG.

LISTAGG avec option DISTINCT :

Cette fonctionnalité est fournie avec 19c:

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

ÉDITER:

Oracle 19C LISTAGG DISTINCT

La fonction d'agrégation LISTAGG prend désormais en charge l'élimination des doublons à l'aide du nouveau mot clé DISTINCT. La fonction d'agrégation LISTAGG classe les lignes de chaque groupe dans une requête en fonction de l'expression ORDER BY, puis concatène les valeurs en une seule chaîne. Avec le nouveau mot clé DISTINCT, les valeurs en double peuvent être supprimées de l'expression spécifiée avant la concaténation en une seule chaîne. Cela évite d'avoir à créer un traitement de requête complexe pour trouver les valeurs distinctes avant d'utiliser la fonction d'agrégation LISTAGG. Avec l'option DISTINCT, le traitement pour supprimer les valeurs en double peut être effectué directement dans la fonction LISTAGG. Le résultat est un SQL plus simple, plus rapide et plus efficace.


0

Quelqu'un a-t-il pensé à utiliser une clause PARTITION BY? Cela a fonctionné pour moi dans cette requête pour obtenir une liste des services d'application et l'accès.

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

J'ai dû supprimer ma clause where pour NDA, mais vous voyez l'idée.


Je ne comprends pas comment cette requête prend des éléments distincts pour le LISTAGG. Il semble que vous n'en auriez qu'un T.ACCESS_MODEpar ligne puisque vous groupez par elle?
jpmc26

0

Je pense que cela pourrait aider - CASE la valeur des colonnes à NULL si elle est dupliquée - alors elle n'est pas ajoutée à la chaîne 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   

Résulte en:

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

0

listagg () ignore les valeurs NULL, donc dans un premier temps vous pouvez utiliser la fonction lag () pour analyser si l'enregistrement précédent avait la même valeur, si oui alors NULL, sinon «nouvelle valeur».

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;

Résultats

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

Notez que le deuxième 2 est remplacé par NULL. Vous pouvez maintenant envelopper un SELECT avec le listagg () autour de lui.

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

Résultat

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

Vous pouvez également le faire sur plusieurs colonnes.

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

Résultat

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

0

Vous pouvez le faire via le remplacement RegEx. Voici un exemple:

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

Également publié ici: Oracle - valeurs Listagg uniques


0

Utilisez la fonction listagg_clob créée comme ceci:

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

J'ai écrit une fonction pour gérer cela en utilisant des expressions régulières. Les paramètres in sont: 1) l'appel de listagg lui-même 2) Une répétition du délimiteur

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;

Vous n'avez plus besoin de répéter l'expression régulière à chaque fois que vous faites cela, dites simplement:

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

0

Si vous n'avez pas besoin d'un ordre particulier de valeurs concaténées et que le séparateur peut être une virgule, vous pouvez faire:

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

0

J'ai besoin d'une version DISTINCT de ceci et j'ai fait travailler celui-ci.

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

0

Un aspect ennuyeux avec LISTAGGest que si la longueur totale de la chaîne concaténée dépasse 4000 caractères (limite pour VARCHAR2SQL), l'erreur ci-dessous est levée, ce qui est difficile à gérer dans les versions Oracle jusqu'à 12.1

ORA-01489: le résultat de la concaténation des chaînes est trop long

Une nouvelle fonctionnalité ajoutée dans 12cR2 est la ON OVERFLOWclause de LISTAGG. La requête comprenant cette clause ressemblerait à ceci:

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

Ce qui précède limitera la sortie à 4000 caractères mais ne lèvera pas l' ORA-01489erreur.

Voici quelques-unes des options supplémentaires de la ON OVERFLOWclause:

  • ON OVERFLOW TRUNCATE 'Contd..' : Ceci s'affichera 'Contd..'à la fin de la chaîne (la valeur par défaut est ...)
  • ON OVERFLOW TRUNCATE '' : Cela affichera les 4000 caractères sans aucune chaîne de fin.
  • ON OVERFLOW TRUNCATE WITH COUNT: Ceci affichera le nombre total de caractères à la fin après les caractères de fin. Par exemple: - ' ...(5512)'
  • ON OVERFLOW ERROR: Si vous vous attendez à ce que le LISTAGGéchoue avec l' ORA-01489erreur (qui est par défaut de toute façon).

0

J'ai implémenté cette fonction stockée:

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;

Je suis désolé, mais dans certains cas (pour un très grand ensemble), Oracle pourrait renvoyer cette erreur:

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.

mais je pense que c'est un bon point de départ;)


0

select col1, listaggr(col2,',') within group(Order by col2) from table group by col1 ce qui signifie agréger les chaînes (col2) en liste en gardant l'ordre n, puis traiter les doublons en tant que groupe par col1, ce qui signifie fusionner les doublons col1 en 1 groupe. peut-être que cela semble propre et simple comme il se doit et si, au cas où vous voudriez également col3, vous devez simplement ajouter un autre listagg () qui estselect col1, listaggr(col2,',') within group(Order by col2),listaggr(col3,',') within group(order by col3) from table group by col1


0

Utiliser SELECT DISTINCT ...dans le cadre d'une sous-requête avant d'appeler LISTAGG est probablement le meilleur moyen pour les requêtes simples, comme indiqué par @a_horse_with_no_name

Cependant, dans les requêtes plus complexes, cela peut ne pas être possible ou facile. J'ai eu cela dans un scénario qui utilisait une approche top-n utilisant une fonction analytique.

J'ai donc trouvé la COLLECTfonction d'agrégation. Il est documenté pour avoir le modificateur UNIQUEou DISTINCTdisponible. Seulement en 10g , il échoue tranquillement (il ignore le modificateur sans erreur). Cependant, pour surmonter cela, à partir d' une autre réponse , je suis arrivé à cette solution:

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

Fondamentalement, en utilisant SET, je supprime les doublons de ma collection.

Vous devrez toujours définir le tab_typcomme un type de collection de base, et dans le cas d'un VARCHAR, ce serait par exemple:

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

Aussi comme correction à la réponse de @a_horse_with_no_name sur la situation multi-colonnes, où vous voudrez peut-être encore agréger sur une troisième (ou plus) colonnes:

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;

Si vous laissiez la rn = 1comme condition where à la requête, vous agrégeriez les autres colonnes de manière incorrecte.


0

Très simple - utilisez dans votre requête une sous-requête avec une sélection distincte:

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

Le moyen le plus simple de gérer plusieurs listagg est d'utiliser 1 WITH (facteur de sous-requête) par colonne contenant une listagg de cette colonne à partir d'une sélection distincte:

    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)

Qui donne:

col1  col2List  col3List
1     2,3,4,5   3,4,6
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.