Avant Oracle 11.2, j'utilisais une fonction d'agrégation personnalisée pour concaténer une colonne en ligne. 11.2 Ajout de la fonction LISTAGG
, donc j'essaie de l'utiliser à la place. Mon problème est que je dois éliminer les doublons dans les résultats et ne semble pas être en mesure de le faire.
Voici un exemple.
CREATE TABLE ListAggTest AS (
SELECT rownum Num1, DECODE(rownum,1,'2',to_char(rownum)) Num2 FROM dual
CONNECT BY rownum<=6
);
SELECT * FROM ListAggTest;
NUM1 NUM2
---------- ---------------------
1 2
2 2 << Duplicate 2
3 3
4 4
5 5
6 6
Ce que je veux voir, c'est ceci:
NUM1 NUM2S
---------- --------------------
1 2-3-4-5-6
2 2-3-4-5-6
3 2-3-4-5-6
4 2-3-4-5-6
5 2-3-4-5-6
6 2-3-4-5-6
Voici une version listagg
qui est proche, mais n'élimine pas les doublons.
SELECT Num1, listagg(Num2,'-') WITHIN GROUP (ORDER BY NULL) OVER () Num2s
FROM ListAggTest;
J'ai une solution, mais c'est pire que de continuer à utiliser la fonction d'agrégation personnalisée.
Vous pouvez utiliser des expressions régulières et regexp_replace
pour supprimer les doublons après concaténation avec listagg
:
SELECT Num1,
RTRIM(
REGEXP_REPLACE(
(listagg(Num2,'-') WITHIN GROUP (ORDER BY Num2) OVER ()),
'([^-]*)(-\1)+($|-)',
'\1\3'),
'-') Num2s
FROM ListAggTest;
Cela pourrait être plus net si l'arôme regex d'Oracle supportait les groupes de recherche ou de non-capture, mais ce n'est pas le cas .
Cependant, cette solution évite d'analyser la source plus d'une fois.
DBFiddle ici
Pour autant que je puisse voir, avec la spécification de langue actuellement disponible, c'est la plus courte pour atteindre ce que vous voulez si cela doit être fait avec listagg
.
select distinct
a.Num1,
b.num2s
from listaggtest a cross join (
select listagg(num2d, '-') within group (order by num2d) num2s
from (
select distinct Num2 num2d from listaggtest
)
) b;
Quelle était votre solution qui était pire que la solution d'agrégation personnalisée?
Bien qu'il s'agisse d'un ancien article avec une réponse acceptée, je pense que la fonction analytique LAG () fonctionne bien dans ce cas et est remarquable:
Voici le code proposé:
with nums as (
SELECT
num1,
num2,
decode( lag(num2) over (partition by null order by num2), --get last num2, if any
--if last num2 is same as this num2, then make it null
num2, null,
num2) newnum2
FROM ListAggTest
)
select
num1,
--listagg ignores NULL values, so duplicates are ignored
listagg( newnum2,'-') WITHIN GROUP (ORDER BY Num2) OVER () num2s
from nums;
Les résultats ci-dessous semblent correspondre à ce que souhaite le PO:
NUM1 NUM2S
1 2-3-4-5-6
2 2-3-4-5-6
3 2-3-4-5-6
4 2-3-4-5-6
5 2-3-4-5-6
6 2-3-4-5-6
Créez un fonction d'agrégation personnalisée pour ce faire.
La base de données Oracle fournit un certain nombre de fonctions d'agrégation prédéfinies telles que MAX, MIN, SUM pour effectuer des opérations sur un ensemble d'enregistrements. Ces fonctions d'agrégation prédéfinies ne peuvent être utilisées qu'avec des données scalaires. Cependant, vous pouvez créer vos propres implémentations personnalisées de ces fonctions, ou définir des fonctions d'agrégation entièrement nouvelles, à utiliser avec des données complexes, par exemple, avec des données multimédia stockées à l'aide de types d'objets, de types opaques et de LOB.
Les fonctions d'agrégation définies par l'utilisateur sont utilisées dans les instructions SQL DML tout comme les agrégats intégrés à la base de données Oracle. Une fois ces fonctions enregistrées auprès du serveur, la base de données appelle simplement les routines d'agrégation que vous avez fournies au lieu des routines natives.
Les agrégats définis par l'utilisateur peuvent également être utilisés avec des données scalaires. Par exemple, il peut être utile de mettre en œuvre des fonctions d'agrégation spéciales pour travailler avec des données statistiques complexes associées à des applications financières ou scientifiques.
Les agrégats définis par l'utilisateur sont une fonctionnalité du cadre d'extensibilité. Vous les implémentez à l'aide des routines d'interface ODCIAggregate.
Voici ma solution au problème qui, à mon avis, n'est pas aussi agréable que d'utiliser notre fonction d'agrégation personnalisée qui existe déjà.
SELECT Num1, listagg(Num2,'-') WITHIN GROUP (ORDER BY NULL) OVER () Num2s FROM (
SELECT Num1, DECODE(ROW_NUMBER() OVER (PARTITION BY Num2 ORDER BY NULL),
1,Num2,NULL) Num2 FROM ListAggTest
);
Vous pouvez également utiliser une instruction collect, puis écrire une fonction pl/sql personnalisée qui convertit la collection en chaîne.
CREATE TYPE varchar2_ntt AS TABLE OF VARCHAR2(4000);
CREATE TYPE varchar2_ntt AS TABLE OF VARCHAR2(4000);
select cast(collect(distinct num2 order by num2) as varchar2_ntt)
from listaggtest
Vous pouvez utiliser distinct
et order by
dans une clause collect
mais s'il est combiné, le distinct
ne fonctionnera pas à partir du 11.2.0.2 :(
La solution de contournement pourrait être une sous-sélection:
select collect(num2 order by num2)
from
(
select distinct num2
from listaggtest
)
Utilisez plutôt WMSYS.WM_Concat.
SELECT Num1, Replace(Wm_Concat(DISTINCT Num2) OVER (), ',', '-')
FROM ListAggTest;
Remarque: cette fonction n'est pas documentée et n'est pas prise en charge. Voir https://forums.Oracle.com/forums/message.jspa?messageID=4372641#4372641 .
J'ai créé cette solution avant de rencontrer ListAgg, mais il y a encore des occasions, comme ce problème de valeur en double, alors cet outil est utile. La version ci-dessous a 4 arguments pour vous donner le contrôle des résultats.
Explication CLOBlist prend le constructeur CLOBlistParam comme paramètre. CLOBlistParam a 4 arguments
string VARCHAR2(4000) - The variable to be aggregated
delimiter VARCHAR2(100) - The delimiting string
initiator VARCHAR2(100) - An initial string added before the first value only.
no_dup VARCHAR2(1) - A flag. Duplicates are suppressed if this is Y
Exemple d'utilisation
--vertical list of comma separated values, no duplicates.
SELECT CLOBlist(CLOBlistParam(column_name,chr(10)||',','','Y')) FROM user_tab_columns
--simple csv
SELECT CLOBlist(CLOBlistParam(table_name,',','','N')) FROM user_tables
Le lien vers Gist est ci-dessous.
https://Gist.github.com/peter-genesys/d203bfb3d88d5a5664a86ea6ee34eeca]1
-- Program : CLOBlist
-- Name : CLOB list
-- Author : Peter Burgess
-- Purpose : CLOB list aggregation function for SQL
-- RETURNS CLOB - to allow for more than 4000 chars to be returned by SQL
-- NEW type CLOBlistParam - allows for definition of the delimiter, and initiator of sequence
------------------------------------------------------------------
--This is an aggregating function for use in SQL.
--It takes the argument and creates a comma delimited list of each instance.
WHENEVER SQLERROR CONTINUE
DROP TYPE CLOBlistImpl;
WHENEVER SQLERROR EXIT FAILURE ROLLBACK
create or replace type CLOBlistParam as object(
string VARCHAR2(4000)
,delimiter VARCHAR2(100)
,initiator VARCHAR2(100)
,no_dup VARCHAR2(1) )
/
show error
--Creating CLOBlist()
--Implement the type CLOBlistImpl to contain the ODCIAggregate routines.
create or replace type CLOBlistImpl as object
(
g_list CLOB, -- progressive concatenation
static function ODCIAggregateInitialize(sctx IN OUT CLOBlistImpl)
return number,
member function ODCIAggregateIterate(self IN OUT CLOBlistImpl
, value IN CLOBlistParam) return number,
member function ODCIAggregateTerminate(self IN CLOBlistImpl
, returnValue OUT CLOB
, flags IN number) return number,
member function ODCIAggregateMerge(self IN OUT CLOBlistImpl
, ctx2 IN CLOBlistImpl) return number
)
/
show error
--Implement the type body for CLOBlistImpl.
create or replace type body CLOBlistImpl is
static function ODCIAggregateInitialize(sctx IN OUT CLOBlistImpl)
return number is
begin
sctx := CLOBlistImpl(TO_CHAR(NULL));
return ODCIConst.Success;
end;
member function ODCIAggregateIterate(self IN OUT CLOBlistImpl
, value IN CLOBlistParam) return number is
begin
IF self.g_list IS NULL THEN
self.g_list := value.initiator||value.string;
ELSIF value.no_dup = 'Y' AND
value.delimiter||self.g_list||value.delimiter LIKE '%'||value.delimiter||value.string||value.delimiter||'%'
THEN
--Do not include duplicate value
NULL;
ELSE
self.g_list := self.g_list||value.delimiter||value.string;
END IF;
return ODCIConst.Success;
end;
member function ODCIAggregateTerminate(self IN CLOBlistImpl
, returnValue OUT CLOB
, flags IN number) return number is
begin
returnValue := self.g_list;
return ODCIConst.Success;
end;
member function ODCIAggregateMerge(self IN OUT CLOBlistImpl
, ctx2 IN CLOBlistImpl) return number is
begin
self.g_list := LTRIM( self.g_list||','||ctx2.g_list,',');
return ODCIConst.Success;
end;
end;
/
show error
--Using CLOBlist() to create a vertical list of comma separated values
-- SELECT CLOBlist(CLOBlistParam(product_code,chr(10)||',','','Y'))
-- FROM account
--DROP FUNCTION CLOBlist
--/
Prompt Create the user-defined aggregate.
CREATE OR REPLACE FUNCTION CLOBlist (input CLOBlistParam) RETURN CLOB
PARALLEL_ENABLE AGGREGATE USING CLOBlistImpl;
/
show error
Mon idée est d'implémenter une fonction stockée comme celle-ci:
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;)
Je sais que c'est quelque temps après la publication d'origine, mais c'était le premier endroit que j'ai trouvé après Google pour une réponse au même problème et j'ai pensé que quelqu'un d'autre qui avait atterri ici pourrait être heureux de trouver une réponse succincte qui ne repose pas sur des requêtes trop compliquées ou regexes.
Cela vous donnera le résultat souhaité:
with nums as (
select distinct num2 distinct_nums
from listaggtest
order by num2
) select num1,
(select listagg(distinct_nums, '-') within group (order by 1) from nums) nums2list
from listaggtest;
Essaye celui-là:
select num1,listagg(Num2,'-') WITHIN GROUP (ORDER BY NULL) Num2s
from (
select distinct num1
,b.num2
from listaggtest a
,(
select num2
from listaggtest
) b
order by 1,2
)
group by num1
Le problème avec d'autres solutions possibles est qu'il n'y a pas de corrélation entre les résultats de la colonne 1 et de la colonne 2. Pour contourner ce problème, la requête interne crée cette corrélation, puis supprime les doublons de cet ensemble de résultats. Lorsque vous effectuez la listagg, le jeu de résultats est déjà propre. le problème était davantage lié à l'obtention des données dans un format utilisable.