繁体   English   中英

LISTAGG function:“字符串连接的结果太长”

[英]LISTAGG function: "result of string concatenation is too long"

我正在使用 Oracle SQL 开发者版本 3.0.04。 我试图使用 function LISTAGG将数据分组在一起。

    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

但是,我不断收到错误消息,

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

我很确定 output 可能超过 4000,因为这里提到的 WEB_LINK 是 url 词干和 url 查询的串联值。

有什么办法可以绕过它 go 还是有其他选择?

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

这将返回clob值,因此行数不受限制。

由于聚合字符串可以长于4000个字节,因此不能使用LISTAGG函数。 您可能会创建一个用户定义的聚合函数 ,该函数返回CLOB而不是VARCHAR2 有一个用户定义的聚合示例,该示例在原始的AskTom讨论中返回一个CLOB ,Tim从该第一个讨论链接到该讨论。

您超出了适用于LISTAGG的4000字节的SQL限制

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

解决方法是,可以使用XMLAGG

例如,

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

如果要串联本身具有4000字节的 多个列 ,则可以串联每列的XMLAGG输出,以避免SQL限制为4000字节。

例如,

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;

listagg最近被ISO SQL标准(SQL:2016)涵盖。 作为其一部分,它还具有on overflow子句,Oracle 12cR2支持该子句。

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

on overflow子句支持truncate选项(替代默认的on overflow error行为)。

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

可选的默认值为三个句点(...),如果发生截断,它将作为最后一个元素添加。

如果指定了with count并且发生了截断,则将省略的值的数量放在方括号中并附加到结果中。

有关listaggon overflow子句的更多信息: http : listagg

LISTAGGON OVERFLOW子句是12cR2中添加的新功能。 包含此子句的查询如下所示:

SELECT pid, LISTAGG(Desc, ' ' ON OVERFLOW TRUNCATE ) WITHIN GROUP (ORDER BY seq) AS desc
FROM B GROUP BY pid;

以上将输出限制为4000个字符,但不会引发ORA-01489错误。

这些是ON OVERFLOW子句的一些附加选项:

  • ON OVERFLOW TRUNCATE 'Contd..' :这将在字符串的末尾显示'Contd..' (默认为...
  • ON OVERFLOW TRUNCATE '' :将显示4000个字符而没有任何终止字符串。
  • ON OVERFLOW TRUNCATE WITH COUNT :将在结束字符之后的末尾显示字符总数。 例如: ...(5512) '
  • ON OVERFLOW ERROR :如果您期望LISTAGG失败并出现ORA-01489错误(无论如何都是默认值)。

12c R2中的LISTAGG增强功能

我可以容忍将我的字段分成多行,每行少于4000个字符的限制-请执行以下操作:

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

加上已接受的答案。 我遇到了类似的问题,最终使用了一个用户定义的函数,该函数返回clob而不是varchar2。 这是我的解决方案:

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

现在,而不是做

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

我必须这样做

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

管理LISTAGG中的溢出

我们可以使用Database 12c SQL模式匹配函数MATCH_RECOGNIZE返回不超过限制的值列表。

示例代码和以下链接中的更多说明。

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

我们能够使用Oracle LISTAGG解决类似的问题。 在某个时候,我们要分组的内容超过了4K的限制,但是很容易解决,方法是让第一个数据集将前15个项目进行汇总,每个项目都有256K的限制。

更多信息:我们有一些项目,这些项目有变更单,这些变更单又有解释。 为什么不将数据库设置为以256K限制的块形式接受更改文本,这一点尚不清楚,但这是设计约束之一。 因此,将变更说明输入表格的应用程序停止在254K并插入,然后获取下一组文本,如果> 254K则生成另一行,依此类推。因此,我们有一个变更单项目,即1:1。 然后我们将其作为1:n进行解释。 LISTAGG将所有这些串联起来。 我们有RMRKS_SN值,每个注释和/或每个254K字符1。

发现最大的RMRKS_SN是31,所以我进行了第一个数据集SN 0到15的提取,第二个数据集16到30的提取,最后一个数据集31到45的提取-嗨,让我们计划有人为一些更改添加很多解释命令!

在SQL报表中,Tablix绑定到第一个数据集。 要获取其他数据,下面是表达式:

=第一个(Fields!NON_STD_TXT.Value,“ DataSet_EXPLAN”)&First(Fields!NON_STD_TXT.Value,“ ds_EXPLAN_SN_16_TO_30”)&First(Fields!NON_STD_TXT.Value,“ ds_EXPLAN_SN_31_TO_45”)

对于我们来说,由于安全限制,我们必须让数据库组创建功能等。 因此,有了一点创造力,我们就不必进行用户聚合或UDF。

如果您的应用程序具有某种类型的SN可以汇总,则此方法应该有效。 我不知道等效的TSQL是什么-我们很高兴能与Oracle一起处理这份报告,对此LISTAGG是天赐之物。

代码是:

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

在其他两个数据集中,只需为FROM中的子查询选择LISTAGG:

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

从...

WHERE RT.RMRKS_SN BETWEEN 31 AND 45

...

... 等等。

在某些情况下,目的是获取所有DISTINCT LISTAGG密钥,而溢出是由LISTAGG连接所有密钥的事实引起的。

这是一个小例子

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         

如果组很大,则重复的键会很快达到允许的最大长度,您会得到ORA-01489: result of string concatenation is too long

不幸的是,不支持LISTAGG( DISTINCT y, '; ')但可以使用LISTAGG忽略NULL的事实来解决 使用ROW_NUMBER,我们将仅考虑第一个密钥。

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

当然,使用子查询中的GROUP BY x,y可以达到相同的结果。 ROW_NUMBER的优点是,可以使用SUM(z)所示的所有其他聚合函数。

谢谢你的建议。 当连接多个字段时,我遇到了同样的问题,但是即使xmlagg也帮不上忙-我仍然得到了ORA-01489。 经过几次尝试,我找到了原因和解决方法:

  1. 原因:我的xmlagg的字段之一存储大文本;
  2. 解决方案:应用to_clob()函数。

例:

rtrim(xmlagg(xmlelement(t, t.field1 ||'|'|| 
                           t.field2 ||'|'|| 
                           t.field3 ||'|'|| 
                           to_clob(t.field4),'; ').extract('//text()')).GetClobVal(),',')

希望对大家有帮助。

要完成聚合并处理 XML / HTML 内容和 Unicode 字符,请使用以下命令:

SELECT uuid, XMLCAST(XMLAGG(XMLELEMENT(E, TO_NCLOB(text),'') ORDER BY uuid) AS NCLOB) AS text 
GROUP BY UUID
  • XMLCAST 将防止 XML / HTML 被编码
  • TO_NCLOB 会将 Unicode 字符转换为“?” (不理想,但比查询中断更好!)

如果您只需要处理 XML / HTML 而无需担心 Unicode 字符,可以使用

SELECT uuid, XMLCAST(XMLAGG(XMLELEMENT(E, TO_CLOB(text),'') ORDER BY uuid) AS CLOB) AS text 
GROUP BY UUID

这种方法的唯一缺点是您只能 select 分组列和您正在聚合的列。 如果要将结果包含在其他列中,则需要将其放置在公用表表达式 (CTE) 中并将其与 Group By 列连接到同一个表中。

我正在使用名为clob_agg的自定义 function,使用方式如下:

select clob_agg(*detail_column*) 
from *table* 
group by *group_column*

真实例子:

select length(clob_agg(x||'')) fullList
from (select level as x
      from dual
      connect by level < 40000)

output 是: 228887 ==> output 的长度:)

对于定义 function(在 sqlDeveloper 中使用它,dataGrip 或其他一些编辑器有时无法执行):

CREATE OR REPLACE TYPE t_clob_agg AS OBJECT
(
    g_string clob,

    STATIC FUNCTION ODCIAggregateInitialize(sctx IN OUT t_clob_agg)
        RETURN NUMBER,

    MEMBER FUNCTION ODCIAggregateIterate(self IN OUT t_clob_agg,
                                         value IN clob)
        RETURN NUMBER,

    MEMBER FUNCTION ODCIAggregateTerminate(self IN t_clob_agg,
                                           returnValue OUT clob,
                                           flags IN NUMBER)
        RETURN NUMBER,

    MEMBER FUNCTION ODCIAggregateMerge(self IN OUT t_clob_agg,
                                       ctx2 IN t_clob_agg)
        RETURN NUMBER
);
/
SHOW ERRORS


CREATE OR REPLACE TYPE BODY t_clob_agg IS
    STATIC FUNCTION ODCIAggregateInitialize(sctx IN OUT t_clob_agg)
        RETURN NUMBER IS
    BEGIN
        sctx := t_clob_agg(NULL);
        RETURN ODCIConst.Success;
    END;

    MEMBER FUNCTION ODCIAggregateIterate(self IN OUT t_clob_agg,
                                         value IN clob)
        RETURN NUMBER IS
    BEGIN
        -- Concatenate string only when not already existing in the list (=unique)
        SELF.g_string := self.g_string || ',' || value;
        RETURN ODCIConst.Success;
    END;

    MEMBER FUNCTION ODCIAggregateTerminate(self IN t_clob_agg,
                                           returnValue OUT clob,
                                           flags IN NUMBER)
        RETURN NUMBER IS
    BEGIN
        returnValue := RTRIM(LTRIM(SELF.g_string, ','), ',');
        RETURN ODCIConst.Success;
    END;

    MEMBER FUNCTION ODCIAggregateMerge(self IN OUT t_clob_agg,
                                       ctx2 IN t_clob_agg)
        RETURN NUMBER IS
    BEGIN
        SELF.g_string := SELF.g_string || ',' || ctx2.g_string;
        RETURN ODCIConst.Success;
    END;


END;
/
SHOW ERRORS



CREATE OR REPLACE FUNCTION clob_agg(p_input clob)
    RETURN clob
    PARALLEL_ENABLE AGGREGATE USING t_clob_agg;
/
SHOW ERRORS

缺少使用CLOB和substr的12c溢出功能也可以

rtrim(dbms_lob.substr(XMLAGG(XMLELEMENT(E,column_name,',')。EXTRACT('// text()')ORDER BY column_name).GetClobVal(),1000,1),',')

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM