如果在条件表达式中用作 window 函数,listagg 会生成 ORA-01489

listagg produces ORA-01489 if used as window function in conditional expression

我的查询 returns 许多(数千)行。 列 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 表达式会阻止对不相关的行执行 window,但出于某种原因,数据库引擎似乎对所有行都计算 window。 (另请注意,order by 子句会导致查询快速失败 - 没有它,一些行会在失败前返回。)

你能解释一下这种行为的原因吗?我怀疑 window 计算在逻辑上是在 select 子句之前,但没有任何证据。转载于 Oracle 11g,18c 和 19 (livesql)。

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

如果您使用的是 Oracle 12.2 或更高版本,另一个解决方法是使用 LISTAGG 函数的 ON OVERFLOW 逻辑。在查询中使用 LISTAGG (v, ',' ON OVERFLOW TRUNCATE) 允许查询 运行 没有错误,并且不会 运行 包含任何值(至少在示例中)。

好吧,您使用的 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 行中过滤。