oracle - 如何通过数据库 View 重用带有子查询因子的sql

标签 oracle

将以下使用子查询(with data as 子句)的 sql 语句转换为在数据库 View 中使用它的最佳实践是什么。 据我所知,数据库 View 中不支持 with data as 子句(已编辑:Oracle 支持公用表表达式),但在我的例子中,子查询分解提供了性能优势。如果我使用 Common Table Expression 创建数据库 View ,那么这个优势就失去了。

请看我的例子:

查询说明

a_table 数百万个条目,通过 select 语句选择了几千个。

anchor_table 对于 a_table 中的每个条目,在 anchor_table 中存在一个对应的条目。通过此表在运行时确定恰好一行作为 anchor 。请参见下面的示例。

horizo​​n_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

即结果是 anchor 行和上方和下方的树行。

如何在数据库 View 中实现相同的目的?

由于性能问题,我的尝试失败了:

创建数据的 View with data as select above 如上所述使用此 View

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)

感谢您的任何建议:-)

修正案

如果我按照第一条评论中的建议创建 View ,则会遇到相同的性能问题。 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

最佳答案

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

然后将该 View 查询为:

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

此外,您对“数据”的多次引用是不必要的;相同的逻辑可以通过附加的分析功能来传递。

最终 View 定义:

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

我在这里看到的(明智的)计划与我在数据库中看到的计划相同;如果您得到不同的东西,请发送 fiddle 链接。

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

关于oracle - 如何通过数据库 View 重用带有子查询因子的sql,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33080890/

相关文章:

sql - 如何在 Oracle 查询中禁用 PL/SQL

sql - 使用 sqlplus 设置要返回的最大行数

java - JDBC 连接错误 - 无法创建 PoolableConnectionFactory

sql - Oracle 慢查询

java - Hibernate 不创建 Java 类。我能做些什么?

SQL 查询在 2 个不同系统上的同一个 Oracle 数据库上返回不同的结果

oracle - 当使用带有 oracle 驱动程序的数据库/sql 包时,LastInsertId 返回 0

oracle - REGEXP_LIKE 中的 CHR(0)

sql - 使用 rowids 将百万行从一个表更新到另一个 Oracle

oracle - Liferay portlet 非liferay JNDI 数据源null