oracle: как гарантировать, что функция в предложении where будет вызываться только после того, как все остальные предложения where отфильтровали результат? - PullRequest
12 голосов
/ 08 декабря 2011

Я пишу запрос на этот счет:

select * 
from players 
where player_name like '%K% 
  and player_rank<10 
  and check_if_player_is_eligible(player_name) > 1;

Теперь функция check_if_player_is_elitable () тяжелая, и поэтому я хочу, чтобы запрос в достаточной степени фильтровал результаты поиска, а затем запускал эту функцию только для отфильтрованных результатов.

Как я могу убедиться, что вся фильтрация происходит перед выполнением функции, чтобы она выполнялась минимальное количество раз?

Ответы [ 5 ]

16 голосов
/ 08 декабря 2011

Вот два метода, в которых вы можете обмануть Oracle, чтобы он не оценивал вашу функцию до того, как все остальные предложения WHERE были оценены:

  1. Использование rownum

    Использование псевдостолбца rownum в подзапросе заставит Oracle «материализовать» подзапрос.См., Например, этот поток askTom для примеров .

    SELECT *
      FROM (SELECT *
               FROM players
              WHERE player_name LIKE '%K%'
                AND player_rank < 10
                AND ROWNUM >= 1)
     WHERE check_if_player_is_eligible(player_name) > 1
    

    Вот справочная документация "Отмена вложенных подзапросов" :

    Оптимизатор может откатывать большинство подзапросов, за некоторыми исключениями.Эти исключения включают иерархические подзапросы и подзапросы, которые содержат псевдостолбец ROWNUM , один из операторов набора, вложенную статистическую функцию или коррелированную ссылку на блок запроса, который не является непосредственным внешним блоком запроса подзапроса.

  2. Использование CASE

    Используя CASE, вы можете заставить Oracle оценивать вашу функцию только тогда, когда другие условия оцениваются как ИСТИНА.К сожалению, это включает дублирование кода, если вы хотите использовать другие пункты для использования индексов, как в:

    SELECT *
      FROM players
     WHERE player_name LIKE '%K%'
       AND player_rank < 10
       AND CASE 
             WHEN player_name LIKE '%K%'
              AND player_rank < 10 
                THEN check_if_player_is_eligible(player_name) 
           END > 1
    
4 голосов
/ 08 декабря 2011

Существует NO_PUSH_PRED подсказка, чтобы сделать это без участия оценки rownum (в любом случае, это хороший трюк)!

SELECT /*+NO_PUSH_PRED(v)*/*
FROM (
        SELECT *
        FROM players
        WHERE player_name LIKE '%K%'
            AND player_rank < 10
    ) v
 WHERE check_if_player_is_eligible(player_name) > 1
3 голосов
/ 09 декабря 2011

Обычно вы хотите избежать форсирования определенного порядка исполнения.Если данные или запрос изменятся, ваши подсказки и уловки могут иметь неприятные последствия.Обычно лучше предоставлять полезные метаданные в Oracle, чтобы он мог принимать правильные решения за вас.

В этом случае вы можете предоставить лучшую статистику оптимизатора о функции с помощью ASSOCIATE STATISTICS .

Например, если ваша функция очень медленная, потому что она должна читать 50 блоков каждый раз, когда она вызывается:

associate statistics with functions
check_if_player_is_eligible default cost(1000 /*cpu*/, 50 /*IO*/, 0 /*network*/);

По умолчанию Oracle предполагает, что функция выберет строку 1/20 отвремя.Oracle хочет исключить как можно больше строк, изменение селективности должно снизить вероятность выполнения функции в первую очередь:

associate statistics with functions
check_if_player_is_eligible default selectivity 90;

Но это вызывает некоторые другие проблемы.Вы должны выбрать селективность для ВСЕХ возможных условий, 90% точно не всегда будут точными.Стоимость ввода-вывода - это количество выбранных блоков, но стоимость процессора - это «используемые машинные инструкции», что именно это означает?

Существуют более продвинутые способы настройки статистики, например, с использованием Oracle DataКартридж расширяемый оптимизатор .Но картридж данных, вероятно, является одной из самых сложных функций Oracle.

2 голосов
/ 08 декабря 2011

Вы не указали, является ли player.player_name уникальным или нет. Можно предположить, что это так и тогда база данных должна вызывать функцию хотя бы один раз для каждой записи результата.

Но , если player.player_name не является уникальным, вы хотели бы свести к минимуму количество вызовов до count (отличный от player.player_name) раз. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *} * * * * * * * *} * * * * * * * * * * * * * * *} * * * * * * * * * по * * стр.

Вы должны заключить вызов функции в подвыбор, чтобы использовать скалярный кэш подзапроса:

SELECT players.*
FROM   players,
      (select check_if_player_is_eligible(player.player_name) eligible) subq
WHERE  player_name LIKE '%K%'
  AND  player_rank < 10
  AND  ROWNUM >= 1
  AND  subq.eligible = 1
0 голосов
/ 08 декабря 2011

Поместите исходный запрос в производную таблицу, затем поместите дополнительный предикат в предложение where в производной таблице.

select * 
from (
   select * 
   from players 
   where player_name like '%K% 
     and player_rank<10 
) derived_tab1
Where  check_if_player_is_eligible(player_name) > 1;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...