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

标签 oracle oracle11g case listagg

我在 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/

相关文章:

SQL 查询比较两个 varray()

sql - 甲骨文 11g : LISTAGG ignores NULL values

oracle - 监控所有索引

mysql - mysql中使用case将列值转换为行值

sql - oracle 12c - 选择最后一次出现字符后的字符串

c# - ADO.NET 提供程序 'Oracle.ManagedDataAccess.Client' 未在机器或应用程序配置文件中注册,或者无法加载

sql - 同一表中的嵌套 SELECT 和 CASE 语句

class - 如何将 Map 转换为 Scala 中的案例类?

oracle - 获取已建立的 Oracle 11 连接的 IP 地址

plsql - PL/SQL Oracle 存储过程(未找到数据)