Оптимизация, необходимая для вставки в запрос с выбором, имеющим самостоятельное соединение - PullRequest
1 голос
/ 27 февраля 2020

Таблица EMPLOYEE имеет структуру ниже с 5 миллионами строк (10 ^ 6).

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

У нас есть другая таблица 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) 
            )

This становится дорогостоящей операцией, потому что для каждого неактивного сотрудника (не с состоянием 1, 2, 3) он пытается выполнить существующий прогон в одной и той же таблице из 5 миллионов записей O (N ^ 2).

Есть ли способ сделать это плоской операцией O (N)?

Кроме того, допустима ли вставка в запрос или мы должны использовать какую-либо другую конструкцию PL / SQL для сделать вкладыши?

Ответы [ 3 ]

0 голосов
/ 27 февраля 2020

С самостоятельным присоединением:

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)
;
0 голосов
/ 28 февраля 2020

Этот запрос можно преобразовать в O (N), переписав запрос в формат, который поддерживает соединения ha sh. Хотя анализ алгоритма становится хитрым очень быстро, и я не уверен, будет ли новая форма быстрее.

Пример схемы

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 немного странная, но в этом случае я считаю, что она действует как al oop, поэтому мы можем объединить операции 3 и 4/5, чтобы найти общее время выполнения. TABLE ACCESS FULL - это O (N), а INDEX UNIQUE SCAN - это O (LOG (N)). Таким образом, вы должны видеть O (N * LOG (N)) вместо O (N ^ 2).

Если вы видите два полных сканирования таблицы, это будет O (N ^ 2), но тогда вы должны попытаться выяснить, почему Oracle не использует индекс.

Стремление к O (N)

Если вы хотите сравнить данные в O (N), я считаю, что ха sh объединение является единственным вариантом. Ха sh объединения работают только с условиями равенства, и в этом случае я думаю, что 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 |
---------------------------------------------

Новый план более сложный, но он использует соединение ha sh в операции 5, что в теории может быть O (2N). Но есть еще один O (N) FULL TABLE SCAN в строке 4. И есть O (N * LOG (N)) SORT UNIQUE в строке 2, хотя мы надеемся, что N будет намного меньше на этом шаге.

Что лучше?

Вы правильно думаете об этом вопросе - все больше людей должны учитывать сложность алгоритмов своих программ. И, как правило, хорошая идея - преобразовать условие соединения в нечто, поддерживающее соединения ha sh. Но с этим странным соединением я не уверен, что ты увидишь улучшение. Это может быть одним из многих случаев, когда константы и детали реализации перевешивают сложность.

Если вы хотите узнать больше, вот глава , которую я написал об использовании анализа алгоритма для понимания SQL производительность.

0 голосов
/ 27 февраля 2020

Вы можете использовать иерархический запрос, чтобы получить статус менеджера, а также текущий статус 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; он существует только для генерации данных для остальной части запроса.)

...