web-dev-qa-db-fra.com

Fonction LISTAGG: "le résultat de la concaténation de chaîne est trop long"

J'utilise Oracle SQL Developer version 3.0.04. J'ai essayé d'utiliser la fonction LISTAGG pour regrouper les données.

    CREATE TABLE FINAL_LOG AS
    SELECT SESSION_DT, C_IP, CS_USER_AGENT,
    listagg(WEB_LINK, ' ')
        WITHIN GROUP(ORDER BY C_IP, CS_USER_AGENT) "WEB_LINKS"
        FROM webviews
        GROUP BY C_IP, CS_USER_AGENT, SESSION_DT
        ORDER BY SESSION_DT

Cependant, je continue à avoir l'erreur,

SQL Error: ORA-01489: result of string concatenation is too long

Je suis à peu près sûr que la sortie risque d'être supérieure à 4 000, car le WEB_LINK mentionné ici est une valeur concaténée de racine d'URL et de requête d'URL. 

Y a-t-il un moyen de le contourner ou existe-t-il une autre solution?

38
user1874311

Étant donné que la chaîne d'agrégats peut comporter plus de 4 000 octets, vous ne pouvez pas utiliser la fonction LISTAGG. Vous pourriez éventuellement créer une fonction d'agrégat définie par l'utilisateur qui renvoie CLOB au lieu de VARCHAR2. Il existe un exemple d'agrégat défini par l'utilisateur qui renvoie CLOB dans la discussion askTom d'origine à laquelle Tim fait référence depuis la première discussion. 

20
Justin Cave
SELECT RTRIM(XMLAGG(XMLELEMENT(E,colname,',').EXTRACT('//text()') ORDER BY colname).GetClobVal(),',') AS LIST
FROM tablename;

Cela retournera une valeur de clob, donc aucune limite sur les lignes.

68
Ankur Bhutani

Vous dépassez la limite SQL de 4000 octets qui s'applique également à LISTAGG.

SQL> SELECT listagg(text, ',') WITHIN GROUP (
  2  ORDER BY NULL)
  3  FROM
  4    (SELECT to_char(to_date(level,'j'), 'jsp') text FROM dual CONNECT BY LEVEL < 250
  5    )
  6  /
SELECT listagg(text, ',') WITHIN GROUP (
*
ERROR at line 1:
ORA-01489: result of string concatenation is too long

Pour résoudre ce problème, vous pouvez utiliserXMLAGG

Par exemple, 

SQL> SET LONG 2000000
SQL> SET pagesize 50000
SQL> SELECT rtrim(xmlagg(XMLELEMENT(e,text,',').EXTRACT('//text()')
  2                     ).GetClobVal(),',') very_long_text
  3  FROM
  4    (SELECT to_char(to_date(level,'j'), 'jsp') text FROM dual CONNECT BY LEVEL < 250
  5    )
  6  /

VERY_LONG_TEXT
--------------------------------------------------------------------------------
one,two,three,four,five,six,seven,eight,nine,ten,eleven,twelve,thirteen,fourteen
,fifteen,sixteen,seventeen,eighteen,nineteen,twenty,twenty-one,twenty-two,twenty
-three,twenty-four,twenty-five,twenty-six,twenty-seven,twenty-eight,twenty-nine,
thirty,thirty-one,thirty-two,thirty-three,thirty-four,thirty-five,thirty-six,thi
rty-seven,thirty-eight,thirty-nine,forty,forty-one,forty-two,forty-three,forty-f
our,forty-five,forty-six,forty-seven,forty-eight,forty-nine,fifty,fifty-one,fift
y-two,fifty-three,fifty-four,fifty-five,fifty-six,fifty-seven,fifty-eight,fifty-
nine,sixty,sixty-one,sixty-two,sixty-three,sixty-four,sixty-five,sixty-six,sixty
-seven,sixty-eight,sixty-nine,seventy,seventy-one,seventy-two,seventy-three,seve
nty-four,seventy-five,seventy-six,seventy-seven,seventy-eight,seventy-nine,eight
y,eighty-one,eighty-two,eighty-three,eighty-four,eighty-five,eighty-six,eighty-s
even,eighty-eight,eighty-nine,ninety,ninety-one,ninety-two,ninety-three,ninety-f
our,ninety-five,ninety-six,ninety-seven,ninety-eight,ninety-nine,one hundred,one
 hundred one,one hundred two,one hundred three,one hundred four,one hundred five
,one hundred six,one hundred seven,one hundred eight,one hundred nine,one hundre
d ten,one hundred eleven,one hundred twelve,one hundred thirteen,one hundred fou
rteen,one hundred fifteen,one hundred sixteen,one hundred seventeen,one hundred
eighteen,one hundred nineteen,one hundred twenty,one hundred twenty-one,one hund
red twenty-two,one hundred twenty-three,one hundred twenty-four,one hundred twen
ty-five,one hundred twenty-six,one hundred twenty-seven,one hundred twenty-eight
,one hundred twenty-nine,one hundred thirty,one hundred thirty-one,one hundred t
hirty-two,one hundred thirty-three,one hundred thirty-four,one hundred thirty-fi
ve,one hundred thirty-six,one hundred thirty-seven,one hundred thirty-eight,one
hundred thirty-nine,one hundred forty,one hundred forty-one,one hundred forty-tw
o,one hundred forty-three,one hundred forty-four,one hundred forty-five,one hund
red forty-six,one hundred forty-seven,one hundred forty-eight,one hundred forty-
nine,one hundred fifty,one hundred fifty-one,one hundred fifty-two,one hundred f
ifty-three,one hundred fifty-four,one hundred fifty-five,one hundred fifty-six,o
ne hundred fifty-seven,one hundred fifty-eight,one hundred fifty-nine,one hundre
d sixty,one hundred sixty-one,one hundred sixty-two,one hundred sixty-three,one
hundred sixty-four,one hundred sixty-five,one hundred sixty-six,one hundred sixt
y-seven,one hundred sixty-eight,one hundred sixty-nine,one hundred seventy,one h
undred seventy-one,one hundred seventy-two,one hundred seventy-three,one hundred
 seventy-four,one hundred seventy-five,one hundred seventy-six,one hundred seven
ty-seven,one hundred seventy-eight,one hundred seventy-nine,one hundred eighty,o
ne hundred eighty-one,one hundred eighty-two,one hundred eighty-three,one hundre
d eighty-four,one hundred eighty-five,one hundred eighty-six,one hundred eighty-
seven,one hundred eighty-eight,one hundred eighty-nine,one hundred ninety,one hu
ndred ninety-one,one hundred ninety-two,one hundred ninety-three,one hundred nin
ety-four,one hundred ninety-five,one hundred ninety-six,one hundred ninety-seven
,one hundred ninety-eight,one hundred ninety-nine,two hundred,two hundred one,tw
o hundred two,two hundred three,two hundred four,two hundred five,two hundred si
x,two hundred seven,two hundred eight,two hundred nine,two hundred ten,two hundr
ed eleven,two hundred twelve,two hundred thirteen,two hundred fourteen,two hundr
ed fifteen,two hundred sixteen,two hundred seventeen,two hundred eighteen,two hu
ndred nineteen,two hundred twenty,two hundred twenty-one,two hundred twenty-two,
two hundred twenty-three,two hundred twenty-four,two hundred twenty-five,two hun
dred twenty-six,two hundred twenty-seven,two hundred twenty-eight,two hundred tw
enty-nine,two hundred thirty,two hundred thirty-one,two hundred thirty-two,two h
undred thirty-three,two hundred thirty-four,two hundred thirty-five,two hundred
thirty-six,two hundred thirty-seven,two hundred thirty-eight,two hundred thirty-
nine,two hundred forty,two hundred forty-one,two hundred forty-two,two hundred f
orty-three,two hundred forty-four,two hundred forty-five,two hundred forty-six,t
wo hundred forty-seven,two hundred forty-eight,two hundred forty-nine

Si vous voulez concaténer plusieurs colonnes qui ont lui-même 4000 octets , vous pouvez concaténer la sortie XMLAGG de chaque colonne pour éviter la limite SQL de 4000 octets.

Par exemple,

WITH DATA AS
  ( SELECT 1 id, rpad('a1',4000,'*') col1, rpad('b1',4000,'*') col2 FROM dual
  UNION
  SELECT 2 id, rpad('a2',4000,'*') col1, rpad('b2',4000,'*') col2 FROM dual
  )
SELECT ID,
       rtrim(xmlagg(XMLELEMENT(e,col1,',').EXTRACT('//text()') ).GetClobVal(), ',')
       || 
       rtrim(xmlagg(XMLELEMENT(e,col2,',').EXTRACT('//text()') ).GetClobVal(), ',') 
       AS very_long_text
FROM DATA
GROUP BY ID
ORDER BY ID;
13
Lalit Kumar B

listagg a récemment été couvert par le standard ISO SQL (SQL: 2016). Dans ce cadre, il a également obtenu une clause on overflow, prise en charge par Oracle 12cR2.

LISTAGG(<expression>, <separator> ON OVERFLOW …)

La clause on overflow prend en charge une option truncate (à la place du comportement par défaut on overflow error).

ON OVERFLOW TRUNCATE [<filler>] WITH[OUT] COUNT

La valeur optionnelle par défaut est trois points (...) et sera ajoutée comme dernier élément en cas de troncature.

Si with count est spécifié et que la troncature se produit, le nombre de valeurs omises est placé entre parenthèses et ajouté au résultat.

En savoir plus sur la clause on overflow de listagg: http://modern-sql.com/feature/listagg

8
Markus Winand

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

Améliorations apportées à LISTAGG dans 12c R2

6
Kaushik Nayak

Je pouvais tolérer que mon champ soit enchaîné sur plusieurs lignes, chacune étant inférieure à la limite de 4000 caractères - procédez comme suit:

with PRECALC as (select 
                 floor(4000/(max(length(MY_COLUMN)+LENGTH(',')))) as MAX_FIELD_LENGTH
                 from MY_TABLE)
select LISTAGG(MY_COLUMN,',') WITHIN GROUP(ORDER BY floor(rownum/MAX_FIELD_LENGTH), MY_COLUMN)
from MY_TABLE, PRECALC
group by floor(rownum/MAX_FIELD_LENGTH)
;
3
Ferdie

Gestion des débordements dans LISTAGG

Nous pouvons utiliser la fonction de correspondance de modèle SQL de la base de données 12c, MATCH_RECOGNIZE, pour renvoyer une liste de valeurs ne dépassant pas la limite.

Exemple de code et plus d'explications dans le lien ci-dessous.

https://blogs.Oracle.com/datawarehousing/entry/managing_overflows_in_listagg

2
Abhishek Maurya

Ajoutant à la réponse acceptée ... J'ai rencontré un problème similaire et fini par utiliser une fonction définie par l'utilisateur qui renvoyait clob au lieu de varchar2. Voici ma solution:

CREATE OR REPLACE TYPE temp_data FORCE AS OBJECT
(
    temporary_data NVARCHAR2(4000)
)
/

CREATE OR REPLACE TYPE temp_data_table FORCE AS TABLE OF temp_data;
/

CREATE OR REPLACE FUNCTION my_agg_func (p_temp_data_table IN temp_data_table, p_delimiter IN NVARCHAR2)
RETURN CLOB IS
  l_string CLOB;
BEGIN
  FOR i IN p_temp_data_table.FIRST .. p_temp_data_table.LAST LOOP
    IF i != p_temp_data_table.FIRST THEN
      l_string := l_string || p_delimiter;
    END IF;
    l_string := l_string || p_temp_data_table(i).temporary_data;
  END LOOP;
  RETURN l_string;
END my_agg_func;
/

Maintenant, au lieu de faire

LISTAGG(column_to_aggregate, '#any_delimiter#') WITHIN GROUP (ORDER BY column_to_order_by)

Je dois faire ça

my_agg_func (
    cast(
        collect(
            temp_data(column_to_aggregate)
            order by column_to_order_by
        ) as temp_data_table
    ),
    '#any_delimiter#'
)
2
Chiranjib

Dans certains scénarios, l’intention est d’obtenir toutes les clés DISTINCT LISTAGG et le débordement est provoqué par le fait que LISTAGG concatène TOUTES LES CL&EACUTE;S.

Voici un petit exemple

create table tab as
select 
  trunc(rownum/10) x,
  'GRP'||to_char(mod(rownum,4)) y,
  mod(rownum,10) z
 from dual connect by level < 100;


select  
 x,
 LISTAGG(y, '; ') WITHIN GROUP (ORDER BY y) y_lst
from tab
group by x;


        X Y_LST                                                            
---------- ------------------------------------------------------------------
         0 GRP0; GRP0; GRP1; GRP1; GRP1; GRP2; GRP2; GRP3; GRP3               
         1 GRP0; GRP0; GRP1; GRP1; GRP2; GRP2; GRP2; GRP3; GRP3; GRP3         
         2 GRP0; GRP0; GRP0; GRP1; GRP1; GRP1; GRP2; GRP2; GRP3; GRP3         
         3 GRP0; GRP0; GRP1; GRP1; GRP2; GRP2; GRP2; GRP3; GRP3; GRP3         
         4 GRP0; GRP0; GRP0; GRP1; GRP1; GRP1; GRP2; GRP2; GRP3; GRP3         
         5 GRP0; GRP0; GRP1; GRP1; GRP2; GRP2; GRP2; GRP3; GRP3; GRP3         
         6 GRP0; GRP0; GRP0; GRP1; GRP1; GRP1; GRP2; GRP2; GRP3; GRP3         
         7 GRP0; GRP0; GRP1; GRP1; GRP2; GRP2; GRP2; GRP3; GRP3; GRP3         
         8 GRP0; GRP0; GRP0; GRP1; GRP1; GRP1; GRP2; GRP2; GRP3; GRP3         
         9 GRP0; GRP0; GRP1; GRP1; GRP2; GRP2; GRP2; GRP3; GRP3; GRP3         

Si les groupes sont grands, les touches répétées atteignent rapidement la longueur maximale autorisée et vous obtenez le ORA-01489: result of string concatenation is too long

Malheureusement, LISTAGG( DISTINCT y, '; ') n'est pas pris en charge, mais vous pouvez utiliser le fait que LISTAGG ignore les valeurs NULL. En utilisant le ROW_NUMBER, nous ne considérerons que la première clé.

with rn as (
select x,y,z,
row_number() over (partition by x,y order by y) rn
from tab
)
select  
 x,
 LISTAGG( case when rn = 1 then y end, '; ') WITHIN GROUP (ORDER BY y) y_lst,
 sum(z) z 
from rn
group by x
order by x;

         X Y_LST                                       Z
---------- ---------------------------------- ----------
         0 GRP0; GRP1; GRP2; GRP3             45 
         1 GRP0; GRP1; GRP2; GRP3             45 
         2 GRP0; GRP1; GRP2; GRP3             45 
         3 GRP0; GRP1; GRP2; GRP3             45 
         4 GRP0; GRP1; GRP2; GRP3             45 
         5 GRP0; GRP1; GRP2; GRP3             45 
         6 GRP0; GRP1; GRP2; GRP3             45 
         7 GRP0; GRP1; GRP2; GRP3             45 
         8 GRP0; GRP1; GRP2; GRP3             45 
         9 GRP0; GRP1; GRP2; GRP3             45

Bien entendu, le même résultat peut être obtenu en utilisant GROUP BY x,y dans la sous-requête. L’avantage de ROW_NUMBER est que toutes les autres fonctions d’agrégat peuvent être utilisées comme illustré avec SUM(z).

0
Marmite Bomber

Nous avons pu résoudre un problème similaire ici en utilisant Oracle LISTAGG. Il y a eu un moment où ce que nous regroupions dépassait la limite de 4 Ko, mais cela a été facilement résolu en faisant en sorte que le premier ensemble de données utilise les 15 premiers éléments à agréger, chacun ayant une limite de 256 Ko.

Plus d'infos: Nous avons des projets, qui ont des ordres de modification, qui ont à leur tour des explications. Pourquoi la base de données est-elle configurée pour accepter le texte de modification en morceaux de 256 Ko n'est pas connue, mais c'est l'une des contraintes de conception. Ainsi, l'application qui introduit les explications de modification dans la table s'arrête à 254K et insère, puis obtient le prochain groupe de texte et si> 254K génère une autre ligne, etc. Nous avons donc un projet avec un ordre de modification, un 1: 1. Ensuite, nous avons 1: n pour des explications. LISTAGG concatène tous ces éléments. Nous avons les valeurs RMRKS_SN, 1 pour chaque remarque et/ou pour chaque 254 Ko de caractères.

Le plus grand RMRKS_SN s’est avéré être 31, alors j’ai fait le premier jeu de données en extrayant SN 0 à 15, le deuxième jeu de données 16 à 30 et le dernier jeu de données 31 à 45 - hé, prévoyons que quelqu'un ajoute BEAUCOUP d’explications à certains changements ordres!

Dans le rapport SQL, le Tablix est lié au premier jeu de données. Pour obtenir les autres données, voici l'expression:

= Premier (Champs! NON_STD_TXT.Value, "DataSet_EXPLAN") & Premier (Champs! NON_STD_TXT.Value, "ds_EXPLAN_SN_16_TO_30") & Premier (Champs! NON_STD_TXT.Value, "ds_EXPLAN_SN_31_D

Pour nous, le groupe de base de données doit créer des fonctions, etc. à cause de contraintes de sécurité. Donc, avec un peu de créativité, nous n’avions pas à créer un User Aggregate ou un UDF.

Si votre application dispose d'une sorte de SN à agréger, cette méthode devrait fonctionner. Je ne sais pas ce qu'est l'équivalent TSQL - nous avons la chance de traiter avec Oracle pour ce rapport, pour lequel LISTAGG est une aubaine.

Le code est:

SELECT
LT.C_O_NBR AS LT_CO_NUM,
RT.C_O_NBR AS RT_CO_NUM,
LT.STD_LN_ITM_NBR, 
RT.NON_STD_LN_ITM_NBR,
RT.NON_STD_PRJ_NBR, 
LT.STD_PRJ_NBR, 
NVL(LT.PRPSL_LN_NBR, RT.PRPSL_LN_NBR) AS PRPSL_LN_NBR,
LT.STD_CO_EXPL_TXT AS STD_TXT,
LT.STD_CO_EXPLN_T, 
LT.STD_CO_EXPL_SN, 
RT.NON_STD_CO_EXPLN_T,
LISTAGG(RT.RMRKS_TXT_FLD, '') 
    WITHIN GROUP(ORDER BY RT.RMRKS_SN) AS NON_STD_TXT

FROM ...

    WHERE RT.RMRKS_SN BETWEEN 0 AND 15

GROUP BY 
    LT.C_O_NBR,
    RT.C_O_NBR,
    ...

Et dans les 2 autres jeux de données, il suffit de sélectionner le LISTAGG uniquement pour les sous-requêtes du FROM:

SELECT
LISTAGG(RT.RMRKS_TXT_FLD, '') 
    WITHIN GROUP(ORDER BY RT.RMRKS_SN) AS NON_STD_TXT

DE ...

WHERE RT.RMRKS_SN BETWEEN 31 AND 45

...

... etc.

0
Michael Durthaler