gpt4 book ai didi

oracle - LISTAGG 被评估并在无法访问的 case 语句中失败

转载 作者:行者123 更新时间:2023-12-01 12:26:23 25 4
gpt4 key购买 nike

我在 Oracle 中的 LISTAGG 函数中遇到了一些非常不寻常的行为。

我知道 LISTAGG 如果处理超过 4000 个字符就会失败。

因为我知道这一点,所以我有一个 CASE 语句来替换计数超过 100 个字符的单元格,并显示“太多无法计数”消息。

CREATE TABLE EMP (
ID VARCHAR2(401),
DEP VARCHAR2(10)
);

INSERT INTO EMP VALUES (DBMS_RANDOM.string('A', 401), 'FOO'); -- Run exactly 9 times
INSERT INTO EMP VALUES (DBMS_RANDOM.string('A', 5), 'BAR'); -- Run 3 times

为简单起见,让我们忽略计数 > 100 的特殊情况,只说应该排除 FOO,应该包括 BAR。
SELECT DEP,
CASE
WHEN DEP = 'BAR' THEN
LISTAGG(ID, ',')
WITHIN GROUP (ORDER BY NULL)
OVER (PARTITION BY DEP)
ELSE
'Too many to count'
END AS ID_LIST
FROM EMP;

这提供了如下所示的结果(但具有不同的随机字符):

Success

但是,仅添加一行额外的行,使 FOO 部门的总数达到 10 ……
INSERT INTO EMP VALUES (DBMS_RANDOM.string('A', 401), 'FOO'); -- Same as before

导致我们在重新运行相同的选择时遇到异常:
ORA-01489: result of string concatenation is too long  
01489. 00000 - "result of string concatenation is too long"
*Cause: String concatenation result is more than the maximum size.
*Action: Make sure that the result is less than the maximum size.

奇怪的是,即使将 case 语句中的条件更改为 1=2,也会发生这种情况。

我不确定这里发生了什么。似乎 SQL 决定评估该语句,而不管它是否有任何使用它的意图,因此在遇到 4000+ 个字符 LISTAGG 时失败。

我有一些解决我的问题的方法,但我真的很想知道更多关于为什么 SQL 决定(显然)运行 LISTAGG 即使它永远不会被到达。

最佳答案

选择列表列/表达式(包括短路 case 表达式)的最终评估发生在检索数据之后。到那时,任何分组等都已经完成。

这种效果不仅发生在 listagg() ,它可以在返回表达式中的任何聚合或分析函数调用中看到 - 尽管除非有副作用,否则很难发现。

作为演示,我创建了一个简单的包,其中包含一个可以从查询中调用的函数:

create package p as
n number := 0;
function f return number;
end;
/

create package body p as
function f return number as
begin
n := n + 1;
return n;
end;
end;
/

这实质上是模拟特定于 session 的序列;序列也证明了这种行为, but appearently for a different reason所以我不想为此使用一个。

在 case 表达式中调用该函数可以满足您的期望;它仅在条件匹配时调用:
select dep,
case
when dep = 'BAR' then
p.f
else
-1
end as id_list
from emp;

DEP ID_LIST
---------- -------
FOO -1
...
BAR 1
BAR 2
BAR 3
FOO -1

select p.f from dual;

F
----------
4

该函数仅在条件匹配时调用。执行计划只显示全表扫描:
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 13 | 91 | 3 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| EMP | 13 | 91 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------

改为使用聚合调用:
select dep,
case
when dep = 'BAR' then
count(p.f)
else
-1
end as id_list
from emp
group by dep;

DEP ID_LIST
---------- -------
FOO -1
BAR 3

select p.f from dual;

F
----------
18

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 13 | 91 | 4 (25)| 00:00:01 |
| 1 | HASH GROUP BY | | 13 | 91 | 4 (25)| 00:00:01 |
| 2 | TABLE ACCESS FULL| EMP | 13 | 91 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------

...该函数被调用了 13 次而不是 3 次;该计划逐步显示散列组,这必须在评估案例之前发生在所有检索到的行中。

同样对于分析版本:
select dep,
case
when dep = 'BAR' then
count(p.f) over (partition by dep)
else
-1
end as id_list
from emp;

DEP ID_LIST
---------- -------
BAR 3
BAR 3
BAR 3
FOO -1
...

select p.f from dual;

F
----------
32

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 13 | 91 | 4 (25)| 00:00:01 |
| 1 | WINDOW SORT | | 13 | 91 | 4 (25)| 00:00:01 |
| 2 | TABLE ACCESS FULL| EMP | 13 | 91 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------

...再次调用该函数 13 次,因为窗口排序(以及分析计算)在 case 表达式可以被评估之前完成。

所以问题不在于返回表达式(在您的情况下为 listagg())在不应该在 case 表达式中被评估;它正在被评估并在甚至考虑 case 表达式条件之前抛出异常。

关于oracle - LISTAGG 被评估并在无法访问的 case 语句中失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39140508/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com