LISTAGG en Oracle para devolver valores distintos


94

Estoy tratando de usar la LISTAGGfunción en Oracle. Me gustaría obtener solo los valores distintos para esa columna. ¿Hay alguna forma en la que pueda obtener solo los valores distintos sin crear una función o un procedimiento?

  col1 col2 Creado_por
   1 2 Smith 
   1 2 Juan 
   1 3 Ajay 
   1 4 carnero 
   1 5 Jack 

Necesito seleccionar col1 y la LISTAGGde col2 (la columna 3 no se considera). Cuando hago eso, obtengo algo como esto como resultado de LISTAGG: [2,2,3,4,5]

Necesito eliminar el '2' duplicado aquí; Solo necesito los valores distintos de col2 contra col1.



¿Puede mostrar la salida (filas) esperada de la muestra? ¿Qué desea ver si hay más de un valor para col1?
a_horse_with_no_name

La salida esperada del LISTAGG es [2,3,4,5]. El segundo '2' debe eliminarse. Y mi tabla tiene más de 1000 filas.
Priyanth

¿Qué desea ver si hay más de un valor para col1?
a_horse_with_no_name

El código es así: - SELECT col1, LISTAGG (col2, ',') dentro del grupo (ordenar por col2) DESDE la tabla T DONDE .... Entonces, debería mostrar todos los valores distintos de col2 correspondientes a col1, separados por coma.
Priyanth

Respuestas:


77

19c y posteriores:

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

18c y anteriores:

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

Si necesita más columnas, algo como esto podría ser lo que está buscando:

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
Similar a lo que yo tenía en mente también. Si listagges la única función agregada en la consulta, debería hacerlo. Sin embargo, combinarlo con otras funciones agregadas es más complicado.
Andriy M

Si. Mi consulta es similar a esta.
Priyanth

1
@a_horse_with_no_name: La declaración de selección anterior me da valores duplicados. Quiero eliminar los duplicados. col1 col2 Creado por 1 2 Smith 1 2 John 1 3 Ajay 1 4 Ram 1 5 Jack Necesito seleccionar col1 y LISTAGG de col2 (la columna 3 no se considera). Mientras hago eso, obtendré algo como esto como resultado de LISTAGG: -> [2,2,3,4,5] Necesito eliminar el duplicado '2' aquí. Solo necesito los valores distintos de col2 contra col1 .
Priyanth

@a_horse_with_no_name: Probé el código y obtuve el mensaje de error que se muestra a continuación ORA-01489: el resultado de la concatenación de cadenas es demasiado largo 01489. 00000 - "El resultado de la concatenación de cadenas es demasiado largo" * Causa: El resultado de la concatenación de cadenas es mayor que el máximo Talla.
Priyanth

@Priyanth: entonces no tienes suerte. La longitud total supera los 4000 bytes y Oracle no puede manejar eso. Deberá realizar la agregación en el código de su aplicación.
a_horse_with_no_name

47

A continuación, le indicamos cómo resolver su problema.

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

from dual

devoluciones

2,2.1,3,4

De oráculo 19C está integrado, ver aquí

A partir del 18C y antes, pruebe dentro del grupo, consulte aquí

De lo contrario, use expresiones regulares

Responda abajo:

select col1, 

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

Nota: Lo anterior funcionará en la mayoría de los casos; la lista debe estar ordenada, es posible que deba recortar todos los espacios iniciales y finales según sus datos.

Si tiene muchos elementos en un grupo> 20 o tamaños de cadena grandes, es posible que se encuentre con el límite de tamaño de cadena de Oracle 'el resultado de la concatenación de cadenas es demasiado largo'.

Desde Oracle 12cR2 puede suprimir este error, consulte aquí . Alternativamente, ponga un número máximo de miembros en cada grupo. Esto solo funcionará si está bien enumerar solo los primeros miembros. Si tiene cadenas de variables muy largas, es posible que esto no funcione. tendrás que experimentar.

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;

Otra solución (no tan simple) para, con suerte, evitar el límite de tamaño de la cadena de Oracle : el tamaño de la cadena está limitado a 4000. Gracias a esta publicación aquí por 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 - algunos casos de prueba - 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: elementos contenidos en elementos, por ejemplo. 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 gracias a Igor! funciona en todos los casos.

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
Resultado justo, pero no tan simple. Con grandes tamaños de datos con los que se encontrará ORA-01489: result of string concatenation is too long.
Pero

1
No lo llamaría una solución simple pero muy atractiva. No sabía que el número de coincidencia se puede usar en la cadena de búsqueda no solo en la cadena de reemplazo. Brillante.
Peter Krassoi

1
Como advertencia, este método requiere que los valores estén ordenados, de modo que los valores duplicados sean consecutivos. De lo contrario, falla. ¡Pero lo simple es bueno! Y estoy usando este método para mi caso particular. ¡Gracias!
StewS2

2
¡super simple no funciona por más de 3 repeticiones! , por ejemplo, a,b,b,b,b,cse convertiría en a,b,b,c:-( (Oracle 11.2)
Andreas Dietrich

4
@AndreasDietrich - La siguiente solución parece ser siempre correcta:regexp_replace(your_string, '([^,]+)(,\1)*(,|$)', '\1\3')
Egor Skriptunoff

10

puede utilizar la wm_concatfunción indocumentada .

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

esta función devuelve la columna clob, si lo desea, puede usar dbms_lob.substrpara convertir clob a varchar2.


15
No, no uses eso.
Koshinae

1
Esto era exactamente lo que necesitaba y funcionó perfectamente dentro de mi consulta agregada existente en lugar de envolver esa consulta en una externa. ¿Qué tiene de malo usar wm_concat(distinct x)?
Ehryk

1
porque no está documentado y no existe en 12c. pero de todos modos en versiones antiguas creo que es la mejor manera.
Kemalettin Erbakırcı

1
¡Gracias @ kemalettinerbakırcı! @thg debes considerar que si algo no está documentado, no sabes cuáles son sus efectos secundarios y cualquier otro tipo de cosas que la Documentación te diga sobre las funciones documentadas; simplemente lo usa como una caja negra y solo sabe qué palanca hace qué según el folclore.
Koshinae


7

Superé este problema agrupando los valores primero, luego hago otra agregación con el listagg. Algo como esto:

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)

solo un acceso completo a la tabla, relativamente fácil de expandir a consultas más complejas


6

Si la intención es aplicar esta transformación a múltiples columnas, he extendido la solución 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

Esta es la versión 11.2.0.2.0 de Oracle Database 11g Enterprise Edition - Producción de 64 bits.
No pude usar STRAGG porque no hay forma de DISTINCT y ORDER.

El rendimiento escala linealmente, lo cual es bueno, ya que estoy agregando todas las columnas de interés. Lo anterior tomó 3 segundos para 77K filas. Por solo un resumen, .172 segundos. Lo hago con que había una manera de diferenciar varias columnas en una tabla en una sola pasada.


6

Si desea valores distintos en MÚLTIPLES columnas, desea control sobre el orden de clasificación, no desea utilizar una función no documentada que pueda desaparecer y no desea más de un escaneo completo de la tabla, puede encontrar útil esta construcción:

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
Puede ahorrar más tiempo si reemplaza "union" por "union all".
Burkay

4

¿Qué hay de la creación de una función dedicada que hará la parte "distinta":

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

Y luego utilícelo para hacer la agregación:

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

4

Para solucionar el problema de la longitud de la cadena, puede usar XMLAGGque es similar listaggpero devuelve un clob.

Luego puede analizar usando regexp_replacey obtener los valores únicos y luego volverlos a convertir en una cadena usando dbms_lob.substr(). Si tiene una gran cantidad de valores distintos, aún se quedará sin espacio de esta manera, pero en muchos casos el código siguiente debería funcionar.

También puede cambiar los delimitadores que usa. En mi caso, quería '-' en lugar de ',' pero debería poder reemplazar los guiones en mi código y usar comas si lo desea.

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

Esta es una gran idea, es necesario llamar a dbms_xmlgen.convert (cadena, 1) para eliminar y & -> & amp conversiones. Ver el enlace de
ozmike

3

Refinando aún más la corrección de @ YoYo al enfoque basado en row_number () de @ a_horse_with_no_name usando DECODE vs CASE (lo vi aquí ). Veo que @Martin Vrbovsky también tiene esta respuesta de enfoque de caso.

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

El próximo Oracle 19c será compatible DISTINCTcon LISTAGG.

LISTAGG con opción DISTINCT :

Esta característica viene con 19c:

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

EDITAR:

Oracle 19C LISTAGG DISTINCT

La función agregada LISTAGG ahora admite la eliminación de duplicados mediante el uso de la nueva palabra clave DISTINCT. La función agregada LISTAGG ordena las filas para cada grupo en una consulta de acuerdo con la expresión ORDER BY y luego concatena los valores en una sola cadena. Con la nueva palabra clave DISTINCT, los valores duplicados se pueden eliminar de la expresión especificada antes de la concatenación en una sola cadena. Esto elimina la necesidad de crear un procesamiento de consultas complejo para encontrar los valores distintos antes de usar la función agregada LISTAGG. Con la opción DISTINCT, el procesamiento para eliminar valores duplicados se puede realizar directamente dentro de la función LISTAGG. El resultado es SQL más simple, rápido y eficiente.


0

¿Alguien ha pensado en usar una cláusula PARTITION BY? Me funcionó en esta consulta para obtener una lista de los servicios de la aplicación y el acceso.

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

Tuve que eliminar mi cláusula where para NDA, pero entiendes la idea.


No entiendo cómo esta consulta toma elementos distintos para LISTAGG. Parece que solo tendría uno T.ACCESS_MODEpor fila ya que está agrupando por él.
jpmc26

0

Creo que esto podría ayudar: CASE el valor de las columnas en NULL si está duplicado, entonces no se adjunta a la cadena 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   

Resultados 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 () ignora los valores NULL, por lo que en un primer paso podría usar la función lag () para analizar si el registro anterior tenía el mismo valor, si es así, entonces NULL, de lo contrario 'nuevo valor'.

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;

Resultados

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

Tenga en cuenta que el segundo 2 se reemplaza por NULL. Ahora puede envolver un SELECT con listagg () alrededor.

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

Resultado

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

También puede hacer esto en varias columnas.

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

Resultado

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

0

Puede hacerlo mediante el reemplazo de RegEx. Aquí hay un ejemplo:

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

También publicado aquí: Oracle - valores únicos de Listagg


0

Utilice la función listagg_clob creada así:

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

Escribí una función para manejar esto usando expresiones regulares. Los parámetros de entrada son: 1) la llamada listagg en sí misma 2) Una repetición del delimitador

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;

Ahora no tiene que repetir la expresión regular cada vez que hace esto, simplemente diga:

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

0

Si no necesita un orden particular de valores concatenados y el separador puede ser una coma, puede hacer lo siguiente:

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

0

Necesitaba una versión DISTINTA de esto y conseguí que esta funcionara.

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

0

Un aspecto molesto LISTAGGes que si la longitud total de la cadena concatenada supera los 4000 caracteres (límite para VARCHAR2en SQL), se produce el siguiente error, que es difícil de administrar en las versiones de Oracle hasta 12.1

ORA-01489: el resultado de la concatenación de cadenas es demasiado largo

Una nueva característica agregada en 12cR2 es la ON OVERFLOWcláusula de LISTAGG. La consulta que incluye esta cláusula se vería así:

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

Lo anterior restringirá la salida a 4000 caracteres pero no arrojará el ORA-01489 error.

Estas son algunas de las opciones adicionales de ON OVERFLOWcláusula:

  • ON OVERFLOW TRUNCATE 'Contd..' : Esto se mostrará 'Contd..'al final de la cadena (el valor predeterminado es... )
  • ON OVERFLOW TRUNCATE '' : Esto mostrará los 4000 caracteres sin ninguna cadena de terminación.
  • ON OVERFLOW TRUNCATE WITH COUNT: Esto mostrará el número total de caracteres al final después de los caracteres finales. Por ejemplo: - ' ...(5512)'
  • ON OVERFLOW ERROR: Si espera LISTAGGque falle con el ORA-01489error (que es el predeterminado de todos modos).

0

Implementé esta función almacenada:

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;

Lo siento, pero en algún caso (para un conjunto muy grande), Oracle podría devolver este error:

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.

pero creo que este es un buen punto de partida;)


0

select col1, listaggr(col2,',') within group(Order by col2) from table group by col1 lo que significa agregar las cadenas (col2) en la lista manteniendo el orden n y luego tratar los duplicados como grupo por col1, lo que significa fusionar los duplicados col1 en 1 grupo. tal vez esto se vea limpio y simple como debería ser y si en caso de que quiera col3 también solo necesita agregar una listagg () más que esselect col1, listaggr(col2,',') within group(Order by col2),listaggr(col3,',') within group(order by col3) from table group by col1


0

Usar SELECT DISTINCT ...como parte de una subconsulta antes de llamar a LISTAGG es probablemente la mejor manera de realizar consultas simples, como lo señala @a_horse_with_no_name

Sin embargo, en consultas más complejas, puede que no sea posible o fácil lograr esto. Tuve esto en un escenario que estaba usando el enfoque top-n usando una función analítica.

Entonces encontré la COLLECTfunción agregada. Está documentado que tiene el modificador UNIQUEo DISTINCTdisponible. Solo en 10g , falla silenciosamente (ignora el modificador sin error). Sin embargo, para superar esto, de otra respuesta , llegué a esta solución:

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

Básicamente, al usar SET, elimino los duplicados de mi colección.

Aún necesitaría definir tab_typcomo un tipo de colección básico, y en el caso de a VARCHAR, esto sería, por ejemplo:

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

También como una corrección a la respuesta de @a_horse_with_no_name en la situación de múltiples columnas, donde es posible que desee agregar aún en una tercera (o más) columnas:

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 deja la rn = 1condición como where para la consulta, agregaría otras columnas incorrectamente.


0

Muy simple: use en su consulta una subconsulta con una selección distinta:

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

La forma más sencilla de manejar múltiples listagg es usar 1 WITH (factor de subconsulta) por columna que contenga una listagg de esa columna de una selección distinta:

    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)

Lo que da:

col1  col2List  col3List
1     2,3,4,5   3,4,6
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.