it-swarm.dev

Wyeliminuj duplikaty w ListAgg (Oracle)

Przed wersją Oracle 11.2 korzystałem z niestandardowej funkcji agregującej, aby połączyć kolumnę w wiersz. 11.2 Dodano funkcję LISTAGG, więc próbuję jej użyć. Mój problem polega na tym, że muszę wyeliminować duplikaty wyników i wydaje się, że nie jestem w stanie tego zrobić.

Oto przykład.

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

Chcę to zobaczyć:

      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

Oto wersja listagg, która jest zamknięta, ale nie eliminuje duplikatów.

SELECT Num1, listagg(Num2,'-') WITHIN GROUP (ORDER BY NULL) OVER () Num2s 
FROM ListAggTest;

Mam rozwiązanie, ale jest to gorsze niż dalsze korzystanie z niestandardowej funkcji agregującej.

46
Leigh Riffel

Możesz używać wyrażeń regularnych i regexp_replace , aby usunąć duplikaty po połączeniu z listagg:

SELECT Num1, 
       RTRIM(
         REGEXP_REPLACE(
           (listagg(Num2,'-') WITHIN GROUP (ORDER BY Num2) OVER ()), 
           '([^-]*)(-\1)+($|-)', 
           '\1\3'),
         '-') Num2s 
FROM ListAggTest;

Może to być bardziej uporządkowane, jeśli wyrażenia regularne Oracle wspierały uprzedzające grupy lub grupy nie przechwytujące, ale tak nie jest .

Jednak to rozwiązanie pozwala uniknąć skanowania źródła więcej niż raz.

DBFiddle tutaj

O ile widzę, przy obecnie dostępnej specyfikacji języka jest to najkrótszy czas na osiągnięcie tego, co chcesz jeśli należy to zrobić za pomocą 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;

Jakie było twoje rozwiązanie, które było gorsze niż niestandardowe rozwiązanie agregujące?

13

Chociaż jest to stary post z zaakceptowaną odpowiedzią, myślę, że funkcja analityczna LAG () działa dobrze w tym przypadku i jest godna uwagi:

  • LAG () usuwa zduplikowane wartości w kolumnie num2 przy minimalnym koszcie
  • Nie potrzeba trywialnego wyrażenia regularnego do filtrowania wyników
  • Tylko jeden pełny skan tabeli (koszt = 4 w przypadku prostej tabeli przykładowej)

Oto proponowany kod:

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;

Poniższe wyniki wydają się być zgodne z oczekiwaniami 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 
9
RJLyders

Utwórz niestandardowa funkcja agregująca , aby to zrobić.

Baza danych Oracle udostępnia szereg predefiniowanych funkcji agregujących, takich jak MAX, MIN, SUM, do wykonywania operacji na zestawie rekordów. Te predefiniowane funkcje agregujące mogą być używane tylko z danymi skalarnymi. Można jednak tworzyć własne niestandardowe implementacje tych funkcji lub definiować zupełnie nowe funkcje agregujące, aby używać ich ze złożonymi danymi - na przykład z danymi multimedialnymi przechowywanymi przy użyciu typów obiektów, typów nieprzezroczystych i obiektów LOB.

Funkcje agregujące zdefiniowane przez użytkownika są używane w instrukcjach SQL DML, podobnie jak wbudowane agregaty bazy danych Oracle. Po zarejestrowaniu takich funkcji na serwerze baza danych po prostu wywołuje podane przez Ciebie procedury agregacji zamiast natywnych.

Agregaty zdefiniowane przez użytkownika mogą być również używane z danymi skalarnymi. Na przykład warto wdrożyć specjalne funkcje agregujące do pracy ze złożonymi danymi statystycznymi związanymi z aplikacjami finansowymi lub naukowymi.

Agregaty zdefiniowane przez użytkownika są cechą Extensibility Framework. Wdrażasz je za pomocą procedur interfejsu ODCIAggregate.

8
Leigh Riffel

Oto moje rozwiązanie problemu, który moim zdaniem nie jest tak fajny, jak użycie naszej niestandardowej funkcji agregującej, która już istnieje.

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

Możesz także użyć instrukcji Collect, a następnie napisać niestandardową funkcję pl/sql, która konwertuje kolekcję na ciąg znaków.

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

Możesz użyć distinct i order by w klauzuli collect, ale po połączeniu distinct nie będzie działać od 11.2.0.2 :(

Obejściem może być podselekcja:

select collect(num2 order by num2) 
from 
( 
    select distinct num2 
    from listaggtest
)
5
Nico

Zamiast tego użyj WMSYS.WM_Concat.

SELECT Num1, Replace(Wm_Concat(DISTINCT Num2) OVER (), ',', '-')
FROM ListAggTest;

Uwaga: Ta funkcja jest nieudokumentowana i nie jest obsługiwana. Zobacz https://forums.Oracle.com/forums/message.jspa?messageID=4372641#4372641 .

5
Karlos

Stworzyłem to rozwiązanie, zanim zetknąłem się z ListAgg, ale wciąż zdarzają się takie sytuacje, jak na przykład problem zduplikowanej wartości, wtedy to narzędzie jest przydatne. Poniższa wersja zawiera 4 argumenty, które dają ci kontrolę nad wynikami.

Objaśnienie CLOBlist przyjmuje jako parametr parametr CLOBlistParam contructor. CLOBlistParam ma 4 argumenty

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

Przykładowe użycie

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

Link do Gist znajduje się poniżej.

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
2
Peter Burgess

Moim pomysłem jest zaimplementowanie funkcji zapisanej w ten sposób:

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;

Przykro mi, ale w niektórych przypadkach (dla bardzo dużego zestawu) Oracle może zwrócić ten błąd:

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.

ale myślę, że to dobry początek;)

1

Wiem, że to było kiedyś po opublikowaniu, ale to było pierwsze miejsce, które znalazłem po Googlingu, aby uzyskać odpowiedź na ten sam problem i pomyślałem, że ktoś, kto tu wylądował, może być szczęśliwy, aby znaleźć zwięzłą odpowiedź, która nie opiera się na zbyt skomplikowanych zapytaniach lub wyrażenia regularne.

To da pożądany rezultat:

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

Spróbuj tego:

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

Problem z innymi możliwymi rozwiązaniami polega na tym, że nie ma korelacji między wynikami dla kolumny 1 i kolumny 2. Aby obejść ten problem, wewnętrzne zapytanie tworzy tę korelację, a następnie usuwa duplikaty z tego zestawu wyników. Gdy wykonasz listagg, zestaw wyników jest już czysty. problem miał więcej wspólnego z uzyskaniem danych w użytecznym formacie.

0
Kevin