如何通过数据库视图重用带有子查询因子的sql

时间:2015-10-12 12:15:19

标签: oracle

使用子查询(with data as子句)转换以下sql语句以在数据库视图中使用它的最佳做法是什么。 AFAIK数据库视图中不支持with data as子句(编辑:Oracle支持公用表表达式),但在我的情况下,子查询因子提供了性能优势。如果我使用Common Table Expression创建数据库视图,那么这种优势就会丢失。

请看一下我的例子:

查询说明

a_table 选择声明中有数百万条目被选中。

anchor_table 对于a_table中的每个条目,都存在anchor_table中的相应条目。通过此表在运行时确定一行作为锚。见下面的例子。

horizon_table 对于每个选择,在运行时确定一个条目(a_table选择的所有条目具有相同的horizo​​n_id)

请注意:这是一个非常简化的sql,到目前为止工作正常。

实际上,超过20个表连接在一起以获得data的结果。 where子句要复杂得多。 需要更多的horizo​​n_table和anchor_table列来准备子查询中的where条件和结果列表,即将这些表移动到主查询是没有解决方案。

with data as (
  select
  a_table.id,
  a_table.descr,
  horizon_table.offset,
  case
    when anchor_table.a_date = trunc(sysdate) then
    1
    else
    0
  end as anchor,
  row_number() over( 
  order by a_table.a_position_field) as position
  from a_table
  join anchor_table on (anchor_table.id = a_table.anchor_id)
  join horizon_table on (horizon_table.id = a_table.horizon_id)
  where a_table.a_value between 1 and 10000
)
select * 
from data d
where d.position between ( 
    select d1.position - d.offset 
    from data d1 
    where d1.anchor = 1) 
  and ( 
    select d2.position + d.offset 
    from data d2 
    where d2.anchor = 1) 

with data as选择的示例:

id   descr   offset  anchor   position
1    bla     3       0        1
2    blab    3       0        2
5    dfkdj   3       0        3
4    dld     3       0        4
6    oeroe   3       1        5
3    blab    3       0        6
9    dfkdj   3       0        7
14   dld     3       0        8
54   oeroe   3       0        9
...

select * from data

的结果
id   descr   offset  anchor   position
2    blab    3       0        2
5    dfkdj   3       0        3
4    dld     3       0        4
6    oeroe   3       1        5
3    blab    3       0        6
9    dfkdj   3       0        7
14   dld     3       0        8

即。结果是上面和下面的锚行和树行。

如何在数据库视图中实现相同的目标?

我的尝试失败,正如我对性能问题所预期的那样:

在上面选择data选择视图with data as 使用上面的视图

select * 
    from data d
    where d.position between ( 
        select d1.position - d.offset 
        from data d1 
        where d1.anchor = 1) 
      and ( 
        select d2.position + d.offset 
        from data d2 
        where d2.anchor = 1)

感谢您的任何建议:-)

修订

如果我按照第一条评论中的建议创建视图,那么我会遇到相同的性能问题。 Oracle不使用子查询来限制结果。

以下是我的生产查询的执行计划(请点击图片)

a)SQL enter image description here

b)查看 enter image description here

以下是我的测试用例的执行计划

-- Create Testdata table with ~ 1,000,000 entries
insert into a_table
  (id, descr, a_position_field, anchor_id, horizon_id, a_value)
  select level, 'data' || level, mod(level, 10), level, 1, level
    from dual
  connect by level <= 999999;

insert into anchor_table
  (id, a_date)
  select level, trunc(sysdate) - 500000 + level
    from dual
  connect by level <= 999999;

insert into horizon_table (id, offset) values (1, 50);

commit;

-- Create view
create or replace view testdata_vw as
  with data as
   (select a_table.id,
           a_table.descr,
           a_table.a_value,
           horizon_table.offset,
           case
             when anchor_table.a_date = trunc(sysdate) then
              1
             else
              0
           end as anchor,
           row_number() over(order by a_table.a_position_field) as position
      from a_table
      join anchor_table
        on (anchor_table.id = a_table.anchor_id)
      join horizon_table
        on (horizon_table.id = a_table.horizon_id))
  select *
    from data d
   where d.position between
         (select d1.position - d.offset from data d1 where d1.anchor = 1) and
         (select d2.position + d.offset from data d2 where d2.anchor = 1);

-- Explain plan of subquery factoring select statement
explain plan for
  with data as
   (select a_table.id,
           a_table.descr,
           a_value,
           horizon_table.offset,
           case
             when anchor_table.a_date = trunc(sysdate) then
              1
             else
              0
           end as anchor,
           row_number() over(order by a_table.a_position_field) as position
      from a_table
      join anchor_table
        on (anchor_table.id = a_table.anchor_id)
      join horizon_table
        on (horizon_table.id = a_table.horizon_id)

     where a_table.a_value between 500000 - 500 and 500000 + 500)
  select *
    from data d
   where d.position between
         (select d1.position - d.offset from data d1 where d1.anchor = 1) and
         (select d2.position + d.offset from data d2 where d2.anchor = 1);

select plan_table_output
  from table(dbms_xplan.display('plan_table', null, null));

/*

Note: Size of SYS_TEMP_0FD9D6628_284C5768 ~ 1000 rows

Plan hash value: 1145408420

----------------------------------------------------------------------------------------------------------
| Id  | Operation                  | Name                        | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT           |                             |     1 |    62 |  1791   (2)| 00:00:31 |
|   1 |  TEMP TABLE TRANSFORMATION |                             |       |       |            |          |
|   2 |   LOAD AS SELECT           | SYS_TEMP_0FD9D6628_284C5768 |       |       |            |          |
|   3 |    WINDOW SORT             |                             |    57 |  6840 |  1785   (2)| 00:00:31 |
|*  4 |     HASH JOIN              |                             |    57 |  6840 |  1784   (2)| 00:00:31 |
|*  5 |      TABLE ACCESS FULL     | A_TABLE                     |    57 |  4104 |  1193   (2)| 00:00:21 |
|   6 |      MERGE JOIN CARTESIAN  |                             |  1189K|    54M|   586   (2)| 00:00:10 |
|   7 |       TABLE ACCESS FULL    | HORIZON_TABLE               |     1 |    26 |     3   (0)| 00:00:01 |
|   8 |       BUFFER SORT          |                             |  1189K|    24M|   583   (2)| 00:00:10 |
|   9 |        TABLE ACCESS FULL   | ANCHOR_TABLE                |  1189K|    24M|   583   (2)| 00:00:10 |
|* 10 |   FILTER                   |                             |       |       |            |          |
|  11 |    VIEW                    |                             |    57 |  3534 |     2   (0)| 00:00:01 |
|  12 |     TABLE ACCESS FULL      | SYS_TEMP_0FD9D6628_284C5768 |    57 |  4104 |     2   (0)| 00:00:01 |
|* 13 |    VIEW                    |                             |    57 |   912 |     2   (0)| 00:00:01 |
|  14 |     TABLE ACCESS FULL      | SYS_TEMP_0FD9D6628_284C5768 |    57 |  4104 |     2   (0)| 00:00:01 |
|* 15 |    VIEW                    |                             |    57 |   912 |     2   (0)| 00:00:01 |
|  16 |     TABLE ACCESS FULL      | SYS_TEMP_0FD9D6628_284C5768 |    57 |  4104 |     2   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------

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

   4 - access("HORIZON_TABLE"."ID"="A_TABLE"."HORIZON_ID" AND 
              "ANCHOR_TABLE"."ID"="A_TABLE"."ANCHOR_ID")
   5 - filter("A_TABLE"."A_VALUE">=499500 AND "A_TABLE"."A_VALUE"<=500500)
  10 - filter("D"."POSITION">= (SELECT "D1"."POSITION"-:B1 FROM  (SELECT + CACHE_TEMP_TABLE 
              ("T1")  "C0" "ID","C1" "DESCR","C2" "A_VALUE","C3" "OFFSET","C4" "ANCHOR","C5" "POSITION" FROM 
              "SYS"."SYS_TEMP_0FD9D6628_284C5768" "T1") "D1" WHERE "D1"."ANCHOR"=1) AND "D"."POSITION"<= 
              (SELECT "D2"."POSITION"+:B2 FROM  (SELECT + CACHE_TEMP_TABLE ("T1")  "C0" "ID","C1" 
              "DESCR","C2" "A_VALUE","C3" "OFFSET","C4" "ANCHOR","C5" "POSITION" FROM 
              "SYS"."SYS_TEMP_0FD9D6628_284C5768" "T1") "D2" WHERE "D2"."ANCHOR"=1))
  13 - filter("D1"."ANCHOR"=1)
  15 - filter("D2"."ANCHOR"=1)

Note
-----
   - dynamic sampling used for this statement (level=4)

*/

-- Explain plan of database view
explain plan for
  select *
    from testdata_vw
   where a_value between 500000 - 500 and 500000 + 500;

select plan_table_output
  from table(dbms_xplan.display('plan_table', null, null));

/*

Note: Size of SYS_TEMP_0FD9D662A_284C5768 ~ 1000000 rows

Plan hash value: 1422141561

-------------------------------------------------------------------------------------------------------------------
| Id  | Operation                   | Name                        | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |                             |  2973 |   180K|       | 50324   (1)| 00:14:16 |
|   1 |  VIEW                       | TESTDATA_VW                 |  2973 |   180K|       | 50324   (1)| 00:14:16 |
|   2 |   TEMP TABLE TRANSFORMATION |                             |       |       |       |            |          |
|   3 |    LOAD AS SELECT           | SYS_TEMP_0FD9D662A_284C5768 |       |       |       |            |          |
|   4 |     WINDOW SORT             |                             |  1189K|   136M|   147M| 37032   (1)| 00:10:30 |
|*  5 |      HASH JOIN              |                             |  1189K|   136M|       |  6868   (1)| 00:01:57 |
|   6 |       TABLE ACCESS FULL     | HORIZON_TABLE               |     1 |    26 |       |     3   (0)| 00:00:01 |
|*  7 |       HASH JOIN             |                             |  1189K|   106M|    38M|  6860   (1)| 00:01:57 |
|   8 |        TABLE ACCESS FULL    | ANCHOR_TABLE                |  1189K|    24M|       |   583   (2)| 00:00:10 |
|   9 |        TABLE ACCESS FULL    | A_TABLE                     |  1209K|    83M|       |  1191   (2)| 00:00:21 |
|* 10 |    FILTER                   |                             |       |       |       |            |          |
|* 11 |     VIEW                    |                             |  1189K|    70M|       |  4431   (1)| 00:01:16 |
|  12 |      TABLE ACCESS FULL      | SYS_TEMP_0FD9D662A_284C5768 |  1189K|    81M|       |  4431   (1)| 00:01:16 |
|* 13 |     VIEW                    |                             |  1189K|    18M|       |  4431   (1)| 00:01:16 |
|  14 |      TABLE ACCESS FULL      | SYS_TEMP_0FD9D662A_284C5768 |  1189K|    81M|       |  4431   (1)| 00:01:16 |
|* 15 |     VIEW                    |                             |  1189K|    18M|       |  4431   (1)| 00:01:16 |
|  16 |      TABLE ACCESS FULL      | SYS_TEMP_0FD9D662A_284C5768 |  1189K|    81M|       |  4431   (1)| 00:01:16 |
-------------------------------------------------------------------------------------------------------------------

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

   5 - access("HORIZON_TABLE"."ID"="A_TABLE"."HORIZON_ID")
   7 - access("ANCHOR_TABLE"."ID"="A_TABLE"."ANCHOR_ID")
  10 - filter("D"."POSITION">= (SELECT "D1"."POSITION"-:B1 FROM  (SELECT + CACHE_TEMP_TABLE ("T1")  
              "C0" "ID","C1" "DESCR","C2" "A_VALUE","C3" "OFFSET","C4" "ANCHOR","C5" "POSITION" FROM 
              "SYS"."SYS_TEMP_0FD9D662A_284C5768" "T1") "D1" WHERE "D1"."ANCHOR"=1) AND "D"."POSITION"<= (SELECT 
              "D2"."POSITION"+:B2 FROM  (SELECT + CACHE_TEMP_TABLE ("T1")  "C0" "ID","C1" "DESCR","C2" 
              "A_VALUE","C3" "OFFSET","C4" "ANCHOR","C5" "POSITION" FROM "SYS"."SYS_TEMP_0FD9D662A_284C5768" "T1") "D2" 
              WHERE "D2"."ANCHOR"=1))
  11 - filter("A_VALUE">=499500 AND "A_VALUE"<=500500)
  13 - filter("D1"."ANCHOR"=1)
  15 - filter("D2"."ANCHOR"=1)

Note
-----
   - dynamic sampling used for this statement (level=4)
*/

sqlfiddle

解释sql http://www.sqlfiddle.com/#!4/6a7022/3

的计划

解释视图计划http://www.sqlfiddle.com/#!4/6a7022/2

2 个答案:

答案 0 :(得分:3)

您需要编写一个视图定义,它将a_value的所有可选范围返回为两列,start_a_value和end_a_value,以及属于每个开始/结束范围的所有记录。换句话说,正确的视图定义应该在逻辑上描述| n ^ 3 |在a_table中给出n行的结果集。

然后将该视图查询为:

SELECT * FROM testdata_vw WHERE START_A_VALUE = 4950 AND END_A_VALUE = 5050;

此外,您多次引用&#34;数据&#34;没必要;可以通过附加的分析功能提供相同的逻辑。

最终视图def:

CREATE OR REPLACE VIEW testdata_vw AS
SELECT  * 
FROM    
    (
    SELECT  T.*, 
            MAX(CASE WHEN ANCHOR=1 THEN POSITION END) 
                OVER (PARTITION BY START_A_VALUE, END_A_VALUE) ANCHOR_POS 
    FROM 
        (
        SELECT  S.A_VALUE                                       START_A_VALUE, 
                E.A_VALUE                                       END_A_VALUE, 
                B.ID                                            ID,
                B.DESCR                                         DESCR,
                HORIZON_TABLE.OFFSET                            OFFSET,
                CASE
                  WHEN ANCHOR_TABLE.A_DATE = TRUNC(SYSDATE) 
                    THEN 1
                    ELSE 0
                  END                                           ANCHOR,
                ROW_NUMBER() 
                    OVER(PARTITION BY S.A_VALUE, E.A_VALUE 
                         ORDER BY B.A_POSITION_FIELD)           POSITION
        FROM 
                A_TABLE S 
        JOIN    A_TABLE E 
                    ON S.A_VALUE<E.A_VALUE 
        JOIN    A_TABLE B 
                    ON B.A_VALUE BETWEEN S.A_VALUE AND E.A_VALUE
        JOIN    ANCHOR_TABLE
                    ON  ANCHOR_TABLE.ID = B.ANCHOR_ID
        JOIN    HORIZON_TABLE
                    ON HORIZON_TABLE.ID = B.HORIZON_ID
        ) T
    ) T
WHERE POSITION BETWEEN ANCHOR_POS - OFFSET AND ANCHOR_POS+OFFSET;

编辑:SQL Fiddle with expected execution plan

我在我的数据库中看到了同样的(明智的)计划;如果你得到不同的东西,请发送小提琴链接。

  1. 使用索引查找在&#34; S&#34;中查找1行A_TABLE(A_VALUE = 4950)
  2. 使用索引查找在&#34; E&#34;中找到1行A_TABLE(A_VALUE = 5050)
  3. 嵌套循环连接#1和#2(1 x 1连接,仍然是1行)
  4. 来自HORIZON表的FTS 1行
  5. 笛卡尔加入#1和#2(1 x 1,可以使用笛卡儿)。
  6. 使用索引查找在&#34; B&#34;中查找~100行A_TABLE,值介于4950和5050之间。
  7. 笛卡尔加入#5和#6(1 x 102,可以使用笛卡儿)。
  8. FTS ANCHOR_TABLE,哈希加入#7。
  9. 分析函数的窗口排序

答案 1 :(得分:1)

您在视图外部有一个谓词,并且您希望在视图中应用。

为此,您可以使用push_pred提示:

select /*+PUSH_PRED(v)*/
  *
from 
  testdata_vw v
where 
  a_value between 5000 - 50 and 5000 + 50;

SQLFIDDLE

编辑:现在我已经看到您使用数据子查询三次了。对于第一次出现,推送谓词是有意义的,但对于d1和d2,它并不是。这是另一个问题。

我该怎么做才能使用两个上下文变量,根据我的需要设置它们并编写查询:

SYS_CONTEXT(&#39; my_context_name&#39;,&#39; var5000&#39;);

create or replace view testdata_vw as
with data as (
  select
  a_table.id,
  a_table.descr,
  horizon_table.offset,
  case
    when anchor_table.a_date = trunc(sysdate) then
    1
    else
    0
  end as anchor,
  row_number() over( 
  order by a_table.a_position_field) as position
  from a_table
  join anchor_table on (anchor_table.id = a_table.anchor_id)
  join horizon_table on (horizon_table.id = a_table.horizon_id)
  where a_table.a_value between SYS_CONTEXT('my_context_name', 'var5000') - SYS_CONTEXT('my_context_name', 'var50') and SYS_CONTEXT('my_context_name', 'var5000') + SYS_CONTEXT('my_context_name', 'var50')
)
select * 
from data d
where d.position between ( 
    select d1.position - d.offset 
    from data d1 
    where d1.anchor = 1) 
  and ( 
    select d2.position + d.offset 
    from data d2 
    where d2.anchor = 1) ;

使用它:

dbms_session.set_context ('my_context_name', 'var5000', 5000);
dbms_session.set_context ('my_context_name', 'var50', 50);

select * from testdata_vw;

UPDATE:您可以在评论时使用包变量,而不是上下文变量(可以在会话中使用)。