2016-12-21 2 views
1

У меня есть ситуация, когда довольно простой запрос выбора занимает несколько минут; это утверждение выглядит следующим образом:SQL - Приоритезация условий WHERE

SELECT * 
    FROM MyView 
WHERE MyFunction(Col_1, Col_2, Col_3, Col_4) = 1 
    AND Col_8 = 20 
; 

Теперь время распределяется (примерно) в 1/3 для выбора и 2/3 для вызова MyFunction (измеряется просто комментируя вызов функции и сравнение с полным выберите время).

Теперь второе условие (т. Е. Col_8 = 20) уже уменьшает количество записей.

Я попытался выполнить тот же запрос дважды, сначала запустив вызов функции и получив его вторым, и оба возвратили одинаковые значения (конечно), но также заняли одинаковое время.

мне было интересно, как предотвратить вызов функции, если первое условие уже не удалось, и мысль о двух альтернатив:

  1. Установите первое условие, как Col_8 = 20 и второй как случай первого (т. е. если первый неудачный возврат FALSE в противном случае вызывает функцию),

  2. Создайте запрос как выбор в пределах выбора.

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

<condition 1> AND THEN <condition 2>... 

Любая полезная дума быть оцененным.

+1

Если добавить индекс 'col_8' время выполнения должно сокращаться только до времени, необходимого функции –

+2

Похоже, вы используете скалярнозначную функцию. Знаете ли вы, что SVF - это дьявол, а использование встроенных табличных функций (TVF) - лучшая идея почти во всех случаях? –

+0

Спасибо @juergend. Что касается названия, то это, конечно, вопрос. Реальная база данных имеет более значимые имена. Как индекс индекса, предположим, что ** view ** является заданным и не может быть изменен. – FDavidov

ответ

2

Скалярная функция - проблема!

Эти операции заставляют оптимизатор выполнять операции RBAR, т. Е. Сканирование таблицы.

Дополнительная информация: http://www.sqlservercentral.com/articles/T-SQL/135321/

Так исправить, вам нужно бункером скалярную функцию! У вас есть несколько вариантов ...

  1. Перемещения логической функции INLINE
  2. Перепишите скалярную функцию, чтобы быть таблицей функции

Я побегу через эти параметры, используя следующий код, как [очень] основные приближение вашей скалярной функции для того, чтобы проиллюстрировать этот момент:

CREATE FUNCTION MyFunction (
    @a int 
, @b int 
, @c int 
, @d int 
) 
    RETURNS bit 
AS 
    BEGIN 
    DECLARE @return_value bit = 0; 
    IF @a + @b + @c + @c > 5 
     BEGIN 
     SET @return_value = 1; 
     END 
    ; 

    RETURN @return_value 
    END 
; 

Переместить логику рядный:

SELECT * 
FROM MyView 
WHERE Col_8 = 20 
AND Col_1 + Col_2 + Col_3 + Col_4 > 5 
; 

Сделать TVF:

CREATE FUNCTION MyNewFunction (
    @a int 
, @b int 
, @c int 
, @d int 
) 
    RETURNS TABLE 
AS 
    RETURN 
SELECT Cast(CASE WHEN @a + @b + @c + @c > 5 THEN 1 ELSE 0 END AS bit) AS return_value 
; 

Затем вызовите его

SELECT * 
FROM MyView 
CROSS 
APPLY dbo.MyNewFunction(Col_1, Col_2, Col_3, Col_4) AS x 
WHERE MyView.Col_8 = 20 
AND x.return_value = 1 
; 
+0

это выбор внутри выделенного мною, который я упомянул, а также упомянул, что функция потребляет 2/3 времени. – FDavidov

+0

это не заставляет порядок исполнения. См. Https://connect.microsoft.com/SQLServer/feedback/details/537419/sql-server-should-not-raise-illogical-errors –

+0

Огромный NO. подзапросы не гарантируют порядок выполнения. –

1

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

У меня есть другое предложение, не уверен, что оно улучшит производительность. Используйте полученную таблицу:

SELECT * 
INTO TMP_FOR_SELECT 
FROM MyView 
WHERE Col_8 = 20; 

SELECT * FROM TMP_FOR_SELECT 
WHERE MyFunction(Col_1, Col_2, Col_3, Col_4) = 1; 

Другое то, что я могу только предположить, индекс по Col_8, который поможет оптимизатор найти результат быстрее.

+0

Это не гарантирует ничего больше, чем производная таблица или CTE (т. Е. Совсем нет) –

+1

+1, это единственный надежный способ принудительного выбора фильтров. Однако вы, возможно, случайно использовали синтаксис ORACLE вместо SQL Server. SQL Server является «SELECT ... INTO TMP_FOR_SELECT FROM ...». Кроме того, я бы использовал реальную временную таблицу, т. Е. '# Tmp_for_select'. – Heinzi

+0

@Heinzi Да, вы правы: P, мой плохой. – sagi

1
SELECT * 
FROM MyView 
WHERE case 
      when Col_8 = 20 
      then case 
        when MyFunction(Col_1, Col_2, Col_3, Col_4) = 1 
        then 1 
       end 
     end = 1 
; 

И если у вас есть индекс по Col_8

SELECT  * 

FROM  MyView 

WHERE  Col_8 = 20 

     and case 
       when Col_8 = 20 
       then case 
         when MyFunction(Col_1, Col_2, Col_3, Col_4) = 1 
         then 1 
        end 
      end = 1 
; 
+0

Эй, Дуду. Да, это один из вариантов, которые я представил в моем вопросе, но надеялся, что есть что-то большее ** ELLEGANT **. Благодарю. – FDavidov

2

Функции в WHERE заявление плохо, так как они должны быть выполнены для каждой строки. Кроме того, индексирование в этом случае невозможно. Если your function is deterministic и столбцы, которые он использует, поступают из одной таблицы, вы можете использовать ее в базовой таблице вашего представления, чтобы создать постоянный вычисленный столбец. Эта колонка может быть использована в вашем WHERE заявления, а также может быть проиндексирована для повышения производительности:

ALTER TABLE MyBaseTable 
ADD ComputedCol AS MyFunction(Col_1, Col_2, Col_3, Col_4) PERSISTED 

Вы можете выбрать с помощью вычисляемого столбца:

SELECT * 
FROM MyView 
WHERE ComputedCol = 1 AND Col_8 = 20 
+0

Благодарим вас за ответ. Мой вопрос не был сфокусирован на изменении вещей в существующей реализации, а на способности приоритизировать/установить порядок выполнения для условий в предложении WHERE. – FDavidov

Смежные вопросы