2016-08-25 7 views
3

OracleのLISTAGG関数で、非常に異常な動作が発生しました。LISTAGGが評価され、到達不能のケースステートメントで失敗する

4000文字を超える文字を扱うと、LISTAGGが失敗することがわかります。

私はこれを知っているので、100文字以上のセルを "数が多すぎる"というメッセージで置き換えるCASE文がありました。

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 

simplicitiesのために、のはカウント> 100のために私の特別なケースを無視してみましょう、とちょうどそのFOOが除外されなければならないと言う、とBARが含まれなければなりません。 10にFOO部門に合計をもたらし、1つだけ余分な行を追加し、

Success

しかし:

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; 

これは、(異なるランダムな文字ではなく)このようになっているはずな結果を提供します...

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を実行することに決めた理由についてもっと知りたいと思います。

答えて

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だから私はこれを1つ使いたくなかった。

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。プランはステップごとにハッシュ・グループを表示します。これは、ケースが評価される前に検索されたすべての行にわたって発生していなければなりません。同様に分析バージョン

:case式が評価される前にウィンドウのソート(ひいては分析計算)が行われたとして再度機能が13回呼び出された

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 | 
--------------------------------------------------------------------------- 

...、。

したがって、実際には、ケース式内でリターン式(あなたの場合はlistagg())が評価されているわけではありません。 case式の条件が考慮される前に評価され、例外がスローされます。

+0

case文の最初の 'when'条件が' 1 = 0'のときにオプティマイザがうまくやっていないのは変です。オプティマイザは、1 = 0のときはcaseを書き換え、else '多くの場合end_id_list'を' id_list'としてカウントするには多すぎます。すべてがコンパイルに進む前に書き直してください。オプティマイザは、クエリの解析の他の部分では非常に巧妙ですが、この場合は明らかにそうではありません。 – mathguy

+0

数字比較(1 = 0)のための最適化を行うべきですが、文字列の比較( 'T' = 'F')ではないことを示唆するMOSノートがあります。しかし、プロセスでは遅すぎる必要もありますが、非集計バージョンではそうしますが、集計などを心配する必要がある場合は、クエリー・リライトの規模が大きすぎる可能性があります。見ている... –

+0

ありがとうアレックス、それは非常に徹底的な説明だった。私は自分自身でこれについて説明する計画を立てるべきだと思っていたはずです。 – Addison

関連する問題