2013-08-14 2 views
2

У нас есть процедура PL/SQL, содержащая базовый запрос, возвращающий результаты из обычной таблицы фактов. Значения измерений, на которых основано предложение WHERE в запросе, передаются как параметры. Мои вопросы: Каков наилучший способ построения запроса с использованием этих параметров?Использование параметров массива в хранимой процедуре Oracle sql

Вот некоторые тестовый код:

SET SERVEROUTPUT ON 100000; 

-- build table 
DROP TABLE T_FACT; 
CREATE TABLE T_FACT 
(CUBE_ID NUMBER 
,THE_DATE DATE 
,DIM1 NUMBER 
,DIM2 NUMBER 
,DIM3 NUMBER 
,DIM4 NUMBER 
,DIM5 NUMBER 
,VALUE NUMBER) 
PARTITION BY LIST (CUBE_ID) 
( 
    PARTITION P1 VALUES ('1') 
,PARTITION P2 VALUES ('2') 
,PARTITION P3 VALUES ('3') 
,PARTITION P4 VALUES ('4') 
,PARTITION P5 VALUES ('5') 
,PARTITION PDEFAULT VALUES (DEFAULT) 
); 

CREATE UNIQUE INDEX T_FACT_UK1 ON T_FACT 
(CUBE_ID, THE_DATE, DIM1, DIM2, DIM3, DIM4, DIM5) 
LOCAL ( 
    PARTITION P1 
,PARTITION P2 
,PARTITION P3 
,PARTITION P4 
,PARTITION P5 
,PARTITION PDEFAULT 
); 

ALTER TABLE T_FACT ADD (
    CONSTRAINT T_FACT_UK1 
    UNIQUE (CUBE_ID, THE_DATE, DIM1, DIM2, DIM3, DIM4, DIM5) 
    USING INDEX LOCAL); 

-- add test data 
TRUNCATE TABLE T_FACT; 
INSERT INTO T_FACT 
SELECT MOD(ROWNUM-1,5)+1 AS CUBE_ID 
     ,ADD_MONTHS(TO_DATE('20010101','YYYYMMDD') , MOD(ROWNUM,48) - 1) AS THE_DATE 
     ,MOD(TRUNC((DECODE(ROWNUM-1,0,1,ROWNUM-1))/(5*POWER(30,4))),30)+1 AS DIM1 
     ,MOD(TRUNC((DECODE(ROWNUM-1,0,1,ROWNUM-1))/(5*POWER(30,3))),30)+1 AS DIM2 
     ,MOD(TRUNC((DECODE(ROWNUM-1,0,1,ROWNUM-1))/(5*POWER(30,2))),30)+1 AS DIM3 
     ,MOD(TRUNC((DECODE(ROWNUM-1,0,1,ROWNUM-1))/(5*30)),30)+1 AS DIM4 
     ,MOD(TRUNC((DECODE(ROWNUM-1,0,1,ROWNUM-1))/5),30)+1 AS DIM5 
     ,TRUNC(dbms_random.value(1, 10000)) AS VALUE 
FROM DUAL 
CONNECT BY ROWNUM <= 1000000; 

COMMIT; 

CREATE OR REPLACE TYPE DIM_TYPE AS TABLE OF NUMBER; 
/

-- slow procedure 
CREATE OR REPLACE PROCEDURE P_SLOW 
(
    CubeId_in     IN NUMBER, 
    DateStart_in    IN DATE, 
    DateEnd_in     IN DATE, 
    Dim1_in      IN DIM_TYPE, 
    Dim2_in      IN DIM_TYPE, 
    Dim3_in      IN DIM_TYPE, 
    Dim4_in      IN DIM_TYPE, 
    Dim5_in      IN DIM_TYPE, 
    Data_out     OUT DIM_TYPE 
) 

IS 
    Count1  NUMBER := Dim1_in.COUNT; 
    Count2  NUMBER := Dim2_in.COUNT; 
    Count3  NUMBER := Dim3_in.COUNT; 
    Count4  NUMBER := Dim4_in.COUNT; 
    Count5  NUMBER := Dim5_in.COUNT; 

BEGIN 

    SELECT VALUE 
    BULK COLLECT INTO Data_out 
    FROM T_FACT 
    WHERE CUBE_ID = CubeId_in 
    AND  (THE_DATE BETWEEN DateStart_in AND DateEnd_in) 
    AND  (DIM1 IN (SELECT COLUMN_VALUE FROM TABLE(Dim1_in)) OR Count1 = 0) 
    AND  (DIM2 IN (SELECT COLUMN_VALUE FROM TABLE(Dim2_in)) OR Count2 = 0) 
    AND  (DIM3 IN (SELECT COLUMN_VALUE FROM TABLE(Dim3_in)) OR Count3 = 0) 
    AND  (DIM4 IN (SELECT COLUMN_VALUE FROM TABLE(Dim4_in)) OR Count4 = 0) 
    AND  (DIM5 IN (SELECT COLUMN_VALUE FROM TABLE(Dim5_in)) OR Count5 = 0); 

END P_SLOW; 
/

-- fast procedure 
CREATE OR REPLACE PROCEDURE P_FAST 
(
    CubeId_in     IN NUMBER, 
    DateStart_in    IN DATE, 
    DateEnd_in     IN DATE, 
    Dim1_in      IN DIM_TYPE, 
    Dim2_in      IN DIM_TYPE, 
    Dim3_in      IN DIM_TYPE, 
    Dim4_in      IN DIM_TYPE, 
    Dim5_in      IN DIM_TYPE, 
    Data_out     OUT DIM_TYPE 
) 

IS 
    Count1  NUMBER := Dim1_in.COUNT; 
    Count2  NUMBER := Dim2_in.COUNT; 
    Count3  NUMBER := Dim3_in.COUNT; 
    Count4  NUMBER := Dim4_in.COUNT; 
    Count5  NUMBER := Dim5_in.COUNT; 

BEGIN 

    SELECT VALUE 
    BULK COLLECT INTO Data_out 
    FROM T_FACT 
    WHERE CUBE_ID = CubeId_in 
    AND  (THE_DATE BETWEEN DateStart_in AND DateEnd_in) 
    AND  (DIM1 IN (SELECT COLUMN_VALUE FROM TABLE(Dim1_in))) 
    AND  (DIM2 IN (SELECT COLUMN_VALUE FROM TABLE(Dim2_in))) 
    AND  (DIM3 IN (SELECT COLUMN_VALUE FROM TABLE(Dim3_in))) 
    AND  (DIM4 IN (SELECT COLUMN_VALUE FROM TABLE(Dim4_in))) 
    AND  (DIM5 IN (SELECT COLUMN_VALUE FROM TABLE(Dim5_in))); 

END P_FAST; 
/

DECLARE 
    CubeId_in      NUMBER := 2; 
    DateStart_in     DATE := TO_DATE('20010101','YYYYMMDD'); 
    DateEnd_in     DATE := TO_DATE('20030101','YYYYMMDD'); 
    Dim1_in      DIM_TYPE := DIM_TYPE(1,2,3,6,15,21,25); 
    Dim2_in      DIM_TYPE := DIM_TYPE(1,3,4,6,7,8,9,10); 
    Dim3_in      DIM_TYPE := DIM_TYPE(2,3,4,5,6,7,8,13,14,15); 
    Dim4_in      DIM_TYPE := DIM_TYPE(1,4,21,22,23,24,29); 
    Dim5_in      DIM_TYPE := DIM_TYPE(2,15,21); 
    Data_out      DIM_TYPE; 

    timestart NUMBER; 

BEGIN 

    timestart:=DBMS_UTILITY.GET_TIME(); 

    P_FAST(CubeId_in, DateStart_in, DateEnd_in, Dim1_in, Dim2_in, Dim3_in, 
    Dim4_in, Dim5_in, Data_out); 
    DBMS_OUTPUT.PUT_LINE('Number of data values:'||Data_out.COUNT); 

    DBMS_OUTPUT.PUT_LINE('Fast proc:' || TO_CHAR(DBMS_UTILITY.GET_TIME()-timestart)); 

    timestart:=DBMS_UTILITY.GET_TIME(); 

    P_SLOW(CubeId_in, DateStart_in, DateEnd_in, Dim1_in, Dim2_in, Dim3_in, 
    Dim4_in, Dim5_in, Data_out); 
    DBMS_OUTPUT.PUT_LINE('Number of data values:'||Data_out.COUNT); 

    DBMS_OUTPUT.PUT_LINE('Slow proc:' || TO_CHAR(DBMS_UTILITY.GET_TIME()-timestart)); 

END; 
/

anonymous block completed 
Elapsed: 00:00:00.567 
Number of data values:642 
Fast proc:22 
Number of data values:642 
Slow proc:32 

Причина у меня есть предикаты «CountX = 0» в процедуре P_SLOW потому, что требование процедуры является то, что не все значения измерений должны быть указаны. Например, вызывающий может запросить пропуск в значениях в Dim1_in, но оставить остальные параметры измерения равными нулю, что означало бы получение всех строк, в которых Dim1 in (x, y) и другие значения измерения могут быть любыми.

Проблема с запросом в P_SLOW, однако, заключается в том, что она медленная - вы можете видеть из периода исключения. С другой стороны, P_FAST работает быстро, единственное отличие заключается в том, что в его запросе нет предикатов «OR Countx = 0». Оказывается, добавление какого-либо «ИЛИ» замедляет работу.

Смотрите планы выполнения ниже:

> EXPLAIN PLAN FOR 
    SELECT VALUE 
    FROM T_FACT 
    WHERE CUBE_ID = 3 
    AND  (THE_DATE BETWEEN TO_DATE('20010101','YYYYMMDD') AND TO_DATE('20030101','YYYYMMDD')) 
    AND  (DIM1 IN (SELECT COLUMN_VALUE FROM TABLE(DIM_TYPE(1,2,3))) OR :COUNT1 = 0) 
    AND  (DIM2 IN (SELECT COLUMN_VALUE FROM TABLE(DIM_TYPE(1,2,3))) OR :COUNT2 = 0) 
    AND  (DIM3 IN (SELECT COLUMN_VALUE FROM TABLE(DIM_TYPE(1,2,3))) OR :COUNT3 = 0) 
    AND  (DIM4 IN (SELECT COLUMN_VALUE FROM TABLE(DIM_TYPE(1,2,3))) OR :COUNT4 = 0) 
    AND  (DIM5 IN (SELECT COLUMN_VALUE FROM TABLE(DIM_TYPE(1))) OR :COUNT5 = 0) 
plan FOR succeeded. 
> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY) 
PLAN_TABLE_OUTPUT    
-------------------------------------------------------------------------------------------------------------------------- 
Plan hash value: 1947951911  

---------------------------------------------------------------------------------------------------------------------  
| Id | Operation        | Name  | Rows | Bytes | Cost (%CPU)| Time  | Pstart| Pstop |  
---------------------------------------------------------------------------------------------------------------------  
| 0 | SELECT STATEMENT      |   |  1 | 100 | 291 (0)| 00:00:04 |  |  |  
|* 1 | FILTER        |   |  |  |   |   |  |  |  
| 2 | PARTITION LIST SINGLE    |   | 5934 | 579K| 291 (0)| 00:00:04 | KEY | KEY |  
| 3 | TABLE ACCESS BY LOCAL INDEX ROWID | T_FACT  | 5934 | 579K| 291 (0)| 00:00:04 |  3 |  3 |  
|* 4 |  INDEX RANGE SCAN     | T_FACT_UK1 | 5934 |  | 290 (0)| 00:00:04 |  3 |  3 |  
|* 5 | COLLECTION ITERATOR CONSTRUCTOR FETCH|   |  |  |   |   |  |  |  
|* 6 | COLLECTION ITERATOR CONSTRUCTOR FETCH|   |  |  |   |   |  |  |  
|* 7 | COLLECTION ITERATOR CONSTRUCTOR FETCH|   |  |  |   |   |  |  |  
|* 8 | COLLECTION ITERATOR CONSTRUCTOR FETCH|   |  |  |   |   |  |  |  
|* 9 | COLLECTION ITERATOR CONSTRUCTOR FETCH|   |  |  |   |   |  |  |  
---------------------------------------------------------------------------------------------------------------------  

Predicate Information (identified by operation id): 
--------------------------------------------------- 

    1 - filter((TO_NUMBER(:COUNT1)=0 OR EXISTS (SELECT 0 FROM TABLE() "KOKBF$" WHERE VALUE(KOKBF$)=:B1)) AND    
       (TO_NUMBER(:COUNT2)=0 OR EXISTS (SELECT 0 FROM TABLE() "KOKBF$" WHERE VALUE(KOKBF$)=:B2)) AND    
       (TO_NUMBER(:COUNT3)=0 OR EXISTS (SELECT 0 FROM TABLE() "KOKBF$" WHERE VALUE(KOKBF$)=:B3)) AND    
       (TO_NUMBER(:COUNT4)=0 OR EXISTS (SELECT 0 FROM TABLE() "KOKBF$" WHERE VALUE(KOKBF$)=:B4)) AND    
       (TO_NUMBER(:COUNT5)=0 OR EXISTS (SELECT 0 FROM TABLE() "KOKBF$" WHERE VALUE(KOKBF$)=:B5)))     
    4 - access("CUBE_ID"=3 AND "THE_DATE">=TO_DATE(' 2001-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND     
       "THE_DATE"<=TO_DATE(' 2003-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))  
    5 - filter(VALUE(KOKBF$)=:B1)  
    6 - filter(VALUE(KOKBF$)=:B1)  
    7 - filter(VALUE(KOKBF$)=:B1)  
    8 - filter(VALUE(KOKBF$)=:B1)  
    9 - filter(VALUE(KOKBF$)=:B1)  

Note  
-----  
    - dynamic sampling used for this statement 

36 rows selected 

> EXPLAIN PLAN FOR 
    SELECT VALUE 
    FROM T_FACT 
    WHERE CUBE_ID = 3 
    AND  (THE_DATE BETWEEN TO_DATE('20010101','YYYYMMDD') AND TO_DATE('20030101','YYYYMMDD')) 
    AND  (DIM1 IN (SELECT COLUMN_VALUE FROM TABLE(DIM_TYPE(1,2,3)))) 
    AND  (DIM2 IN (SELECT COLUMN_VALUE FROM TABLE(DIM_TYPE(1,2,3)))) 
    AND  (DIM3 IN (SELECT COLUMN_VALUE FROM TABLE(DIM_TYPE(1,2,3)))) 
    AND  (DIM4 IN (SELECT COLUMN_VALUE FROM TABLE(DIM_TYPE(1,2,3)))) 
    AND  (DIM5 IN (SELECT COLUMN_VALUE FROM TABLE(DIM_TYPE(1)))) 
plan FOR succeeded. 
> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY) 
PLAN_TABLE_OUTPUT    
-------------------------------------------------------------------------------------------------------------------------- 

Plan hash value: 3872369897  

------------------------------------------------------------------------------------------------------------------------- 
| Id | Operation         | Name  | Rows | Bytes | Cost (%CPU)| Time  | Pstart| Pstop | 
------------------------------------------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT       |   |  1 | 110 | 440 (1)| 00:00:06 |  |  | 
|* 1 | HASH JOIN SEMI       |   |  1 | 110 | 440 (1)| 00:00:06 |  |  | 
|* 2 | HASH JOIN SEMI       |   |  1 | 108 | 410 (1)| 00:00:05 |  |  | 
|* 3 | HASH JOIN SEMI       |   |  1 | 106 | 381 (1)| 00:00:05 |  |  | 
|* 4 |  HASH JOIN SEMI       |   |  1 | 104 | 351 (1)| 00:00:05 |  |  | 
|* 5 |  HASH JOIN RIGHT SEMI     |   | 59 | 6018 | 321 (1)| 00:00:04 |  |  | 
| 6 |  COLLECTION ITERATOR CONSTRUCTOR FETCH|   |  |  |   |   |  |  | 
| 7 |  PARTITION LIST SINGLE    |   | 5934 | 579K| 291 (0)| 00:00:04 | KEY | KEY | 
| 8 |  TABLE ACCESS BY LOCAL INDEX ROWID | T_FACT  | 5934 | 579K| 291 (0)| 00:00:04 |  3 |  3 | 
|* 9 |   INDEX RANGE SCAN     | T_FACT_UK1 | 5934 |  | 290 (0)| 00:00:04 |  3 |  3 | 
| 10 |  COLLECTION ITERATOR CONSTRUCTOR FETCH |   |  |  |   |   |  |  | 
| 11 |  COLLECTION ITERATOR CONSTRUCTOR FETCH |   |  |  |   |   |  |  | 
| 12 | COLLECTION ITERATOR CONSTRUCTOR FETCH |   |  |  |   |   |  |  | 
| 13 | COLLECTION ITERATOR CONSTRUCTOR FETCH |   |  |  |   |   |  |  | 
------------------------------------------------------------------------------------------------------------------------- 

Predicate Information (identified by operation id): 
--------------------------------------------------- 

    1 - access("DIM1"=VALUE(KOKBF$)) 
    2 - access("DIM2"=VALUE(KOKBF$)) 
    3 - access("DIM3"=VALUE(KOKBF$)) 
    4 - access("DIM4"=VALUE(KOKBF$)) 
    5 - access("DIM5"=VALUE(KOKBF$)) 
    9 - access("CUBE_ID"=3 AND "THE_DATE">=TO_DATE(' 2001-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND     
       "THE_DATE"<=TO_DATE(' 2003-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))  

Note  
-----  
    - dynamic sampling used for this statement 

35 rows selected 

Теперь я не могу использовать в-список - то есть мы не можем просто преобразовать параметры Dim1_in и т.д., в в-листы и использование динамический SQL, потому что мы могли бы иметь тысячи значений измерения. Я мог бы использовать временные таблицы и скопировать туда массивы, но в нашем реальном случае у нас есть 15 измерений, и производительность может пострадать. Другой вариант, мы знаем, является создание динамического SQL, как:

SELECT VALUE 
FROM T_FACT 
WHERE CUBE_ID = 3 
AND  (THE_DATE BETWEEN TO_DATE('20010101','YYYYMMDD') AND TO_DATE('20030101','YYYYMMDD')) 
AND  (DIM1 IN (SELECT COLUMN_VALUE FROM TABLE(Dim1_in))) 
AND  (DIM2 IN (SELECT COLUMN_VALUE FROM TABLE(Dim2_in))) 
AND  (1=1 OR DIM3 IN (SELECT COLUMN_VALUE FROM TABLE(Dim3_in))) 
AND  (1=1 OR DIM4 IN (SELECT COLUMN_VALUE FROM TABLE(Dim4_in))) 
AND  (1=1 OR DIM5 IN (SELECT COLUMN_VALUE FROM TABLE(Dim5_in))) 

OPEN mycursor 
FOR mysql 
USING Dim1_in, Dim2_in, Dim3_in, Dim4_in, Dim5_in; 

, когда я знаю, что Dim3_in, Dim4_in, Dim5_in являются NULL.

Любые другие варианты, которые вы все можете предложить, будут высоко оценены.

ответ

0

Дело в том, что вы не должны так поступать. Связь между таблицей фактов и атрибутами разных измерений/размеров дает разные отчеты.

1) Вы не хотите, чтобы одна операционная инструкция SELECT управляла таблицей фактов.

2) Сбор в сбор данных за один день прост, на один месяц может быть и в порядке. что насчет года или всей истории?

Лучше всего иметь один оператор SQL на одно требование. Вполне нормально иметь много операторов SQL, даже если они выглядят одинаково. Напишите результаты для правильных сводных таблиц и оттуда.

i.e: нормально иметь одну таблицу с агрегированными данными, а затем другие агрегаты на ее основе.

Смежные вопросы