【问题标题】:listagg produces ORA-01489 if used as window function in conditional expression如果在条件表达式中用作窗口函数,listagg 会产生 ORA-01489
【发布时间】:2021-05-13 14:08:23
【问题描述】:

我的查询返回许多(数千)行。 l 列对于非常少量的行(最多 10 行)具有一定的价值。 对于每个这样的行,我想在所有这些行上输出非常短(最多 5 个字符)varchar 列 v 的聚合逗号分隔值。 对于没有 l 特殊值的行,我只想输出该行的 v 值。

同一问题的综合示例:从前 10000 个整数中,我想为每个个位数输出 1,2,3,4,5,6,7,8,9;该号码为多位数号码。 (是的,愚蠢的例子,但真实案例是有道理的。)

with x (v,l) as (
  select to_char(level), length(to_char(level)) from dual connect by level <= 10000
)
select case l
         when 1 then listagg(v,',') within group (order by v) over (partition by l)
         else v
       end
from x
order by 1;

问题是,listagg 函数因ORA-01489: result of string concatenation is too long 错误而失败。

我知道 listagg 函数的 4000 个字符限制以及基于 xmlagg 的解决方法。我只是没有得到足够的限制来连接我想要连接的数据,即使对于所有数据来说都不够。在上面的例子中,9 个个位数字的分区适合 4000 个字符,9000 个四位数字的分区不适合。我预计case 表达式会阻止执行不相关行的窗口,但由于某种原因,似乎数据库引擎会评估所有行的窗口。 (另请注意,order by 子句会导致查询快速失败 - 没有它,一些行会在失败之前返回。)

您能解释一下这种行为的一些原因吗?我怀疑窗口计算在逻辑上在select 子句之前,但没有任何证据。在 Oracle 11g、18c 和 19 (livesql) 上重现。

【问题讨论】:

    标签: sql oracle case listagg analytic-functions


    【解决方案1】:

    好吧,您使用的是非程序化的SQL,因此您不能指望代码路径的某些部分不会被执行,只是因为它们没有被使用。 (因此按照其他建议填充错误不会成功)。

    无论如何,您都可以根据listagg 忽略null 值这一事实来使用常用技巧。

    所以这个公式很好用:

    with x (v,l) as (
      select to_char(level), length(to_char(level)) from dual connect by level <= 10000
    )
    select   nvl(listagg(case when l = 1 then v end,',') within group (order by v) over (partition by l),v) lst
    from x
    order by 1;
    

    给予

    LST
    ------------------
    1,2,3,4,5,6,7,8,9
    1,2,3,4,5,6,7,8,9
    ..
    10
    100
    1000
    10000
    

    问题的解释可以在执行计划中找到(只显示相关部分)

    ----------------------------------------------------------------------------------------
    | Id  | Operation                       | Name | Rows  | Bytes | Cost (%CPU)| Time     |
    ----------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                |      |     1 |    35 |     4  (50)| 00:00:01 |
    |   1 |  SORT ORDER BY                  |      |     1 |    35 |     4  (50)| 00:00:01 |
    |   2 |   WINDOW SORT                   |      |     1 |    35 |     4  (50)| 00:00:01 |
    |   3 |    VIEW                         |      |     1 |    35 |     2   (0)| 00:00:01 |
    |*  4 |     CONNECT BY WITHOUT FILTERING|      |       |       |            |          |
    |   5 |      FAST DUAL                  |      |     1 |       |     2   (0)| 00:00:01 |
    ----------------------------------------------------------------------------------------
    ...
    Column Projection Information (identified by operation id):
    -----------------------------------------------------------
     
       1 - (#keys=1) CASE "L" WHEN 1 THEN LISTAGG("V",',') WITHIN GROUP ( ORDER BY 
           "V") OVER ( PARTITION BY "L") ELSE "V" END [4000]
       2 - (#keys=2) "L"[NUMBER,22], "V"[VARCHAR2,40], LISTAGG("V",',') WITHIN 
           GROUP ( ORDER BY "V") OVER ( PARTITION BY "L")[4000]
       3 - "V"[VARCHAR2,40], "L"[NUMBER,22]
       4 - LEVEL[4]
    

    因此,在第 2 行中,listagg 的计算(对于所有行)仅在第 1 行中被过滤。

    【讨论】:

    • 谢谢,是的,我显然不能,我希望优化器足够聪明,能够识别它不需要计算大分区。您的查询对于 v=10、l=2 返回 null,我需要返回 10(对于所有后续行类似)。
    • 非常抱歉@TomášZáluský 我用NVL 更新了查询以考虑else 部分。
    • 完美运行。非常聪明的把戏。 (实际上将 nvl 表达式重写为真实代码需要 5 行 SQL 代码,因为删除了重复项,因此需要一些额外的努力,但故事有一个美好的结局。)我也非常感谢你在解释计划中的推理。普遍支持,接受和赞誉:-)
    【解决方案2】:

    奇怪的是,即使没有结果超过 4000 个字符,您也会收到关于 4000 个字符限制的错误。也许您可以将此作为错误提交给 Oracle 支持。

    如果您使用的是 Oracle 12.2 或更高版本,另一种解决方法是利用 LISTAGG 函数的 ON OVERFLOW 逻辑。在查询中使用LISTAGG (v, ',' ON OVERFLOW TRUNCATE) 可以使查询无错误地运行,并且不会截断任何值(至少在示例中)。

    【讨论】:

    • 我首先要在 AskTom 上提问,但不幸的是,目前已禁用提问。是的,如果我的主数据库不是 11g,on overflow 子句将是解决方案......无论如何,谢谢:-)
    猜你喜欢
    • 2013-01-29
    • 2019-03-15
    • 2021-07-04
    • 1970-01-01
    • 2017-02-06
    • 1970-01-01
    • 2014-10-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多