LISTAGG在无法访问的case语句中进行评估和失败

时间:2016-08-25 08:40:04

标签: 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的原因,即使它永远不会达到。

1 个答案:

答案 0 :(得分:4)

选择列表列/表达式的最终评估,包括短路的案例表达,在检索数据之后发生。此时已经完成了任何分组等。

此效果不仅发生在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;
/

这基本上模拟了特定于会话的序列;序列也证明了这种行为,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次,因为窗口排序(因此分析计算)在可以评估案例表达式之前完成。

所以问题实际上不是返回表达式(在你的情况下是listagg())在case表达式中被评估的时候它不应该是;在对案例表达条件进行考虑之前,它正在被评估并抛出异常。