J'essaie d'utiliser la fonction LISTAGG
dans Oracle. Je voudrais obtenir uniquement les valeurs distinctes pour cette colonne. Existe-t-il un moyen de ne pouvoir obtenir que 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 Ram 1 5 Jack
Je dois sélectionner col1 et la variable LISTAGG
de col2 (la colonne 3 n'est pas prise en compte). Lorsque je fais cela, je reçois quelque chose comme ceci à la suite de LISTAGG
: [2,2,3,4,5]
J'ai besoin d'enlever le duplicata '2' ici; Je n'ai besoin que des valeurs distinctes de col2 contre col1.
Voulez-vous dire quelque chose comme ça:
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, voici 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;
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
résultats
2,2,1,3,4
RÉPONSE (voir notes 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 supprimer tout l’espace de fin et de début en fonction de vos données.
Si vous avez beaucoup d'éléments dans un groupe> 20 ou de grandes tailles de chaîne, vous pouvez vous heurter à la limite de taille de chaîne Oracle, car le résultat de la concaténation de chaînes est trop long. Cela ne fonctionnera que si son ok pour ne lister que 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, espérons-le, la limite de taille de chaîne - la taille de chaîne est limitée à 4000. Merci à cet article ici de utilisateur3465996
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;
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
éléments contenus dans des é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
vous pouvez utiliser la fonction wm_concat
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 utiliser dbms_lob.substr
pour convertir clob en varchar2.
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
Ceci est Oracle Database 11g Édition Entreprise Version 11.2.0.2.0 - Production 64 bits .
Je ne pouvais pas utiliser STRAGG car il n'y avait aucun moyen de DISTINCTER et de COMMANDER .
Les performances évoluent de manière linéaire, ce qui est une bonne chose puisque j'ajoute toutes les colonnes d'intérêt. Ce qui précède a pris 3 secondes pour 77 000 lignes. Pour un seul cumul, 0,172 seconde. Je fais avec il y avait un moyen de distinguer plusieurs colonnes dans une table en un seul passage.
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
Pour contourner le problème de longueur de chaîne, vous pouvez utiliser XMLAGG
, qui est similaire à listagg
mais renvoie un clob.
Vous pouvez ensuite analyser en utilisant regexp_replace
et obtenir les valeurs uniques, puis le reconvertir en chaîne à l'aide de dbms_lob.substr()
. Si vous avez une quantité énorme 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
Si vous voulez des valeurs distinctes sur plusieurs colonnes, voulez un contrôle sur l'ordre de tri, ne souhaitez pas utiliser une fonction non documentée pouvant disparaître et ne voulez pas plus d'une analyse complète de la table, cette construction peut être 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
);
Qu'en est-il de créer 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 ensuite, 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;
Oracle 19c à venir supportera DISTINCT
avec LISTAGG
.
LISTAGG avec option DISTINCT :
Cette fonctionnalité arrive avec 19c:
SQL> select deptno, listagg (distinct sal,', ') within group (order by sal) 2 from scott.emp 3 group by deptno;
MODIFIER:
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 ordonne les lignes de chaque groupe dans une requête en fonction de l'expression ORDER BY, puis concatène les valeurs dans 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 dans une chaîne unique. Cela supprime la nécessité de créer un traitement de requête complexe pour rechercher 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.
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 procéder comme suit:
select col1, stragg(distinct col2)
from table
group by col1
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;
Désolé, mais dans certains cas (pour un très gros lot), 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;)
Affinement de la correction de @ YoYo en fonction de l'approche basée sur row_number () de @ a_horse_with_no_name à l'aide de DECODE vs CASE ( j'ai vu ici ). Je vois que @Martin Vrbovsky a aussi cette réponse 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;
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 uniques Listagg
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
listagg () ignore les valeurs NULL. Ainsi, dans un premier temps, vous pouvez utiliser la fonction lag () pour déterminer 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 second 2 est remplacé par NULL. Vous pouvez maintenant envelopper un SELECT avec le 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 );
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
Utiliser SELECT DISTINCT ...
dans le cadre d'une sous-requête avant d'appeler LISTAGG est probablement la meilleure solution pour les requêtes simples, comme indiqué par @a_horse_with_no_name
Cependant, dans des requêtes plus complexes, il pourrait ne pas être possible, ni facile, de réaliser cela. Je l'ai eu dans un scénario qui utilisait l'approche top-n en utilisant une fonction analytique.
J'ai donc trouvé la fonction COLLECT
aggreg. Il est documenté d'avoir le modificateur UNIQUE
ou DISTINCT
disponible. Seulement dans 10g , il échoue silencieusement (il ignore le modificateur sans erreur). Cependant, pour surmonter ceci, de une autre réponse , je suis venu à 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 devez toujours définir le tab_typ
en tant que type de collection de base et, dans le cas d'une VARCHAR
, ce serait par exemple:
CREATE OR REPLACE type tab_typ as table of varchar2(100)
/
Également en tant que correction à la réponse de @a_horse_with_no_name sur la situation à plusieurs colonnes, où vous pourriez vouloir agréger encore 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 laissez le rn = 1
comme condition where dans la requête, vous ne regrouperez pas correctement les autres colonnes.
Je pense que cela pourrait aider - CASE la valeur de la colonne à 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
Un aspect gênant avec LISTAGG
est que si la longueur totale de la chaîne concaténée dépasse 4000 caractères (limite pour VARCHAR2
en SQL), l'erreur ci-dessous est renvoyée, ce qui est difficile à gérer dans les versions Oracle jusqu'à 12.1
ORA-01489: le résultat de la concaténation de chaînes est trop long
Une nouvelle fonctionnalité ajoutée dans 12cR2 est la clause ON OVERFLOW
de LISTAGG
. La requête incluant 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 générera pas l'erreur ORA-01489
.
Voici quelques-unes des options supplémentaires de la clause ON OVERFLOW
:
ON OVERFLOW TRUNCATE 'Contd..'
: Ceci affichera 'Contd..'
à la fin de la chaîne (la valeur par défaut est ...
)ON OVERFLOW TRUNCATE ''
: Ceci affichera les 4000 caractères sans chaîne de fin.ON OVERFLOW TRUNCATE WITH COUNT
: Ceci affichera le nombre total de caractères à la fin après les caractères finaux . Exemple: - '...(5512)
'ON OVERFLOW ERROR
: Si vous vous attendez à ce que LISTAGG
échoue avec l'erreur ORA-01489
(qui est par défaut de toute façon).Le moyen le plus simple de gérer plusieurs listaggs consiste à utiliser 1 WITH (facteur de sous-requête) par colonne contenant un listagg de cette colonne à partir d'un élément sélectionné distinct:
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
J'ai besoin d'une version DISTINCT de celui-ci et celui-ci fonctionne.
RTRIM(REGEXP_REPLACE(
(value, ', ') WITHIN GROUP( ORDER BY value)),
'([^ ]+)(, \1)+','\1'),', ')
select col1, listaggr(col2,',') within group(Order by col2) from table group by col1
signifie agréger les chaînes (col2) dans une liste en conservant l'ordre n, puis traite ensuite les doublons sous forme de groupe par col1, ce qui signifie fusionner les doublons col1 dans un groupe. peut-être que cela a l'air propre et simple comme il se doit et si au cas où vous voudriez col3, vous auriez juste besoin d'ajouter un autre listagg () qui est select col1, listaggr(col2,',') within group(Order by col2),listaggr(col3,',') within group(order by col3) from table group by col1
J'ai écrit une fonction pour gérer cela en utilisant des expressions régulières. Les paramètres in sont: 1) L’appel 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;
Maintenant, vous n'avez plus besoin de répéter l'expression régulière à chaque fois, dites simplement:
select distinct_listagg(
listagg(myfield,', ') within group (order by 1),
', '
)
from mytable;
Quelqu'un a-t-il pensé à utiliser une clause PARTITION BY? Dans cette requête, cela a fonctionné pour obtenir une liste des services d'application et de 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
Je devais couper ma clause where pour NDA, mais vous voyez l'idée.