Сначала выполните рефакторинг подзапроса перед любым другим SQL - PullRequest
8 голосов
/ 11 апреля 2019

У меня очень сложное представление, которое имеет вид ниже

create or replace view loan_vw as 
select * from (with loan_info as (select loan_table.*,commission_table.* 
                                   from loan_table,
                                  commission_table where 
                                  contract_id=commission_id)
                select /*complex transformations */ from loan_info
                where type <> 'PRINCIPAL'
                union all 
                select /*complex transformations */ from loan_info
                where type = 'PRINCIPAL')

Теперь, если я сделаю следующее, выберите запрос, зависает

         select * from loan_vw where contract_id='HA001234TY56';

Но если я жестко закодировал внутри подзапросарефакторинг или использование переменной уровня пакета в том же сеансе, запрос возвращается в секунду

create or replace view loan_vw as 
        select * from (with loan_info as (select loan_table.*,commission_table.* 
                                           from loan_table,
                                          commission_table where 
                                          contract_id=commission_id
                                          and contract_id='HA001234TY56'
                                          )
                        select /*complex transformations */ from loan_info
                        where type <> 'PRINCIPAL'
                        union all 
                        select /*complex transformations */ from loan_info
                        where type = 'PRINCIPAL')

Поскольку я использую бизнес-объект, я не могу использовать переменную уровня пакета

Так что мой вопрос есть подсказка вOracle сообщает оптимизатору сначала проверить значение contract_id в loan_vw в рефакторинге подзапроса

. В соответствии с запросом используется следующая аналитическая функция:

select value_date, item, credit_entry, item_paid
from (
  select value_date, item, credit_entry, debit_entry,
    greatest(0, least(credit_entry, nvl(sum(debit_entry) over (), 0)
      - nvl(sum(credit_entry) over (order by value_date
          rows between unbounded preceding and 1 preceding), 0))) as item_paid
  from your_table
)
where item is not null;

После следования советам, данным Boneist и MarcinJ Iудалил рефакторинг подзапроса (CTE) и написал один длинный запрос, подобный приведенному ниже, который повысил производительность с 3 минут до 0,156 секунд

  create or replace view loan_vw as
  select /*complex transformations */
                               from loan_table,
                              commission_table where 
                              contract_id=commission_id
               and loan_table.type <> 'PRINCIPAL'
  union all
  select /*complex transformations */
                               from loan_table,
                              commission_table where 
                              contract_id=commission_id
               and loan_table.type = 'PRINCIPAL'

Ответы [ 2 ]

4 голосов
/ 11 апреля 2019

Действительно ли эти преобразования настолько сложны, что вы должны использовать UNION ALL? Оптимизировать то, что вы не видите, действительно сложно, но, возможно, вы пытались избавиться от CTE и внедрить свои расчеты в потоке?

CREATE OR REPLACE VIEW loan_vw AS
SELECT loan.contract_id
     , CASE commission.type -- or wherever this comes from
         WHEN 'PRINCIPAL'
         THEN SUM(whatever) OVER (PARTITION BY loan.contract_id, loan.type) -- total_whatever

         ELSE SUM(something_else) OVER (PARTITION BY loan.contract_id, loan.type) -- total_something_else
      END AS whatever_something
  FROM loan_table loan 
 INNER 
  JOIN commission_table commission
    ON loan.contract_id = commission.commission_id

Обратите внимание, что если ваши аналитические функции не имеют PARTITION BY contract_id, вы вообще не сможете использовать индекс для этого столбца contract_id.

Взгляните на эту скрипку БД (вам нужно нажать на ... в последней таблице результатов, чтобы развернуть результаты). Здесь таблица loan имеет индексированный (PK) столбец contract_id, но также some_other_id, который также уникален, но не проиндексирован, и предикат во внешнем запросе все еще находится в contract_id. Если вы сравните планы для partition by contract и partition by other id, вы увидите, что в плане partition by other id индекс вообще не используется: в таблице ссуд есть TABLE ACCESS с FULL опциями по сравнению с INDEX - UNIQUE SCAN в partition by contract. Это очевидно, потому что оптимизатор не может самостоятельно разрешить соотношение между contract_id и some_other_id, и поэтому ему нужно будет запускать SUM или AVG по всему окну, а не ограничивать количество строк в окне посредством использования индекса.

То, что вы также можете попробовать - если у вас есть таблица измерений с этими контрактами, - это присоединить ее к своим результатам и представить contract_id из таблицы измерений вместо наиболее вероятной таблицы фактов огромных ссуд. Иногда это может привести к улучшению оценки количества элементов за счет использования уникального индекса в таблице измерений.

Опять же, очень сложно оптимизировать черный ящик без запроса или даже плана, поэтому мы не знаем, что происходит. Например, CTE или подзапрос могут быть реализованы без необходимости.

1 голос
/ 11 апреля 2019

Спасибо за обновление, включив пример списка столбцов.

Учитывая ваш обновленный запрос, я бы предложил изменить ваше представление (или, возможно, создать второе представление для запроса единичных contract_ids, если ваше исходное представление можно использовать для запроса нескольких contract_ids - если, конечно, это не результаты исходного представления имеет смысл только для отдельных contract_ids!) что-то вроде:

CREATE OR REPLACE VIEW loan_vw AS 
WITH loan_info AS (SELECT l.*, c.* -- for future-proofing, you should list the column names explicitly; if this statement is rerun and there's a column with the same name in both tables, it'll fail.
                   FROM   loan_table l
                          INNER JOIN commission_table c ON l.contract_id = c.commission_id -- you should always alias the join condition columns for ease of maintenance.
                  )
SELECT value_date,
     item,
     credit_entry,
     debit_entry,
     GREATEST(0,
            LEAST(credit_entry,
                NVL(SUM(debit_entry) OVER (PARTITION BY contract_id), 0)
                  - NVL(SUM(credit_entry) OVER (PARTITION BY contract_id ORDER BY value_date ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0))) AS item_paid
FROM   loan_info
WHERE  TYPE <> 'PRINCIPAL'
UNION ALL
SELECT ...
FROM   loan_info
WHERE  TYPE = 'PRINCIPAL';

Обратите внимание, что я преобразовал ваше объединение в синтаксис ANSI, поскольку его легче понять, чем соединения старого стиля (для начала проще отделить условия объединения от предикатов!).

...