2016-08-25 2 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 

단순화를 술 들어, 카운트> (100) 내 특별한 경우를 무시하자, 바로 그 FOO는 제외해야한다고하고, BAR가 포함되어야한다. (10)에 FOO 부서에서 총을 가져, 단지 하나의 여분의 행을 추가,

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 그래서 이것을 사용하고 싶지 않습니다.

대소 문자 표현식에서 해당 함수를 호출하면 예상 한대로 처리됩니다. 조건이 일치하면 호출됩니다.

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())이 평가되고 있지는 않습니다. 케이스 표현 조건이 고려되기 전에 예외가 발생하고 평가됩니다.

+0

case 문에서 첫 번째 'when'조건이 '1 = 0' 일 때 옵티마이 저가 더 나은 작업을 수행하지 않는다는 것은 여전히 ​​이상합니다. 옵티마이 저는 case가 1 = 0이면 else를 '너무 많아서 end_id_list'로 재 작성해야 모든 것이 컴파일되기 전에 id_list'로'너무 많은 것으로 계산할 '수 있습니다. 옵티마이 저는 쿼리 구문 분석의 다른 부분에서 꽤 똑똑하지만이 경우에는 그렇지 않습니다. – mathguy

+0

문자열 비교 ('T'= 'F')가 아닌 숫자 비교 (1 = 0)를위한 최적화를 제안하는 MOS 메모가 있습니다. 그러나 그렇게하더라도 프로세스에서 너무 늦어야합니다 - 집계되지 않은 버전에서는 그렇게 할 것이지만 집계 등에 대해 걱정할 필요가있을 때 쿼리 재 작성의 규모가 너무 클 수도 있습니다. 어쩌면 너무 틈새 일 수 있습니다. 보았다 ... –

+0

감사합니다 알렉스, 그건 아주 철저한 설명이었다. 나는 이것에 대한 설명을 할 생각을해야만했다. – Addison