插入查询需要优化 select 具有自连接

Optimization needed for insert into query with select having self join

A table EMPLOYEE 具有以下结构,其中包含 500 万行 (10^6)。

Name   
------  
EMPNAME
EMPID
MANAGERID (foreign key to same table)
STATUS  

我们有一个不同的 table EmpAct,我们在其中执行插入,如下所示

INSERT INTO empact VALUES
(empName, empid, status)
SELECT empName, empid, status
FROM  employee e
WHERE e1.status in (1, 2, 3) 
      OR
      EXISTS (SELECT 1 
              FROM employee  m 
              WHERE m.empid = e.managerid AND
                 m.status IN (1,2,3) 
            )

这将成为一项成本高昂的操作,因为对于每个非在职员工(不是状态 1、2、3),它都会尝试将存在 运行 放入相同的 table 500 万条记录中 O( N^2) 操作。

有没有办法让它成为平面 O(N) 操作?

此外,插入到查询中是否可以使用,还是我们应该使用其他一些 PL/SQL 构造来进行插入?

您可以使用分层查询来获取经理状态以及当前 emp 的状态,例如:

WITH emp AS (SELECT 'a' empname, 1 empid, 1 status, NULL mgr_id FROM dual UNION ALL
             SELECT 'b' empname, 2 empid, 2 status, 1 mgr_id FROM dual UNION ALL
             SELECT 'c' empname, 3 empid, 4 status, NULL mgr_id FROM dual UNION ALL
             SELECT 'd' empname, 4 empid, 1 status, 3 mgr_id FROM dual UNION ALL
             SELECT 'e' empname, 5 empid, 6 status, 3 mgr_id FROM dual UNION ALL
             SELECT 'f' empname, 6 empid, 3 status, NULL mgr_id FROM dual UNION ALL
             SELECT 'g' empname, 7 empid, 5 status, 6 mgr_id FROM dual),
 initres AS (SELECT empname, empid, status, mgr_id, PRIOR status mgr_status
             FROM   emp
             START WITH mgr_id IS NULL
             CONNECT BY PRIOR empid = mgr_id)
SELECT empname,
       empid,
       status
FROM   initres
WHERE  status IN (1, 2, 3)
OR     mgr_status IN (1, 2, 3);

EMPNAME      EMPID     STATUS
------- ---------- ----------
a                1          1
b                2          2
d                4          1
f                6          3
g                7          5

(您显然不需要 emp 子查询;它只是用来为查询的其余部分生成数据。)

自加入:

insert into empact
select e.empname, e.empid, e.status
from emp e left outer join emp m on e.mgr_id = m.empid
where e.status in (1,2,3) or m.status in (1,2,3)
;

通过将查询重写为支持散列连接的格式,可以将此查询转换为 O(N)。虽然算法分析很快变得很棘手,我不确定新形式是否会更快。

示例架构

create table employee
(
    empname varchar2(100),
    empid number primary key,
    managerid number references employee(empid),
    status number
);
create table empact as select empName, empid, status from employee where 1=0;

insert into employee
select level, level, level, 1 from dual connect by level <= 100000;
begin
    dbms_stats.gather_table_stats(user, 'EMPLOYEE');
end;
/

原始查询 - O(N*LOG(N))

explain plan for
INSERT INTO empact
SELECT empName, empid, status
FROM  employee e
WHERE e.status in (1, 2, 3) 
      OR
      EXISTS (SELECT 1 
              FROM employee  m 
              WHERE m.empid = e.managerid AND
                 m.status IN (1,2,3)
            );

select * from table(dbms_xplan.display(format => 'basic'));


Plan hash value: 961581243

------------------------------------------------------
| Id  | Operation                     | Name         |
------------------------------------------------------
|   0 | INSERT STATEMENT              |              |
|   1 |  LOAD TABLE CONVENTIONAL      | EMPACT       |
|   2 |   FILTER                      |              |
|   3 |    TABLE ACCESS FULL          | EMPLOYEE     |
|   4 |    TABLE ACCESS BY INDEX ROWID| EMPLOYEE     |
|   5 |     INDEX UNIQUE SCAN         | SYS_C0027564 |
------------------------------------------------------

FILTER 操作有点奇怪,但在这种情况下,我认为它就像一个循环,所以我们可以将操作 3 和 4/5 相乘以找到总的 运行 时间. TABLE ACCESS FULL 是 O(N),INDEX UNIQUE SCAN 是 O(LOG(N))。所以你应该看到 O(N*LOG(N)) 而不是 O(N^2).

如果您看到两次完整的 table 扫描,那将是 O(N^2),但是您应该尝试调查为什么 Oracle 没有使用索引。

争取 O(N)

如果你想在 O(N) 中比较数据,我相信散列连接是唯一的选择。散列连接仅适用于相等条件,在这种情况下,我认为 Oracle 不够聪明,无法理解如何将查询重写为常规相等条件。我们可以通过将查询分成两部分并将它们联合在一起来自己完成:

explain plan for
INSERT INTO empact
SELECT empName, empid, status
FROM  employee e
WHERE e.status in (1, 2, 3) 
UNION
SELECT empName, empid, status
FROM  employee e
WHERE EXISTS (SELECT 1 
              FROM employee  m 
              WHERE m.empid = e.managerid AND
                 m.status IN (1,2,3)
            );

select * from table(dbms_xplan.display(format => 'basic'));

Plan hash value: 3147379352

---------------------------------------------
| Id  | Operation                | Name     |
---------------------------------------------
|   0 | INSERT STATEMENT         |          |
|   1 |  LOAD TABLE CONVENTIONAL | EMPACT   |
|   2 |   SORT UNIQUE            |          |
|   3 |    UNION-ALL             |          |
|   4 |     TABLE ACCESS FULL    | EMPLOYEE |
|   5 |     HASH JOIN            |          |
|   6 |      TABLE ACCESS FULL   | EMPLOYEE |
|   7 |      TABLE ACCESS FULL   | EMPLOYEE |
---------------------------------------------

新方案比较复杂,但是在操作5中使用了hash join,理论上可以O(2N)。但是第 4 行还有另一个 O(N) FULL TABLE SCAN。第 2 行有一个 O(N*LOG(N)) SORT UNIQUE,尽管希望 N 在这一步要小得多。

什么最好?

您正在以正确的方式思考这个问题 - 更多的人应该考虑他们程序的算法复杂性。通常将连接条件转换为支持散列连接的东西是个好主意。但是对于这种奇怪的连接条件,我不确定您是否会看到改进。这可能是常量和实现细节超过复杂性的众多情况之一。

如果你想了解更多,here's a chapter我写了一篇关于使用算法分析来理解SQL性能的文章。