Проблемы с производительностью SQL Server при использовании EXCEPT в запросе

В общем, у меня есть запрос SELECT в сочетании с тремя независимыми запросами SELECT. Я использую операторы EXCEPT и UNION в запросе. При самостоятельном выполнении запросов я получу результаты за 1-2 секунды, но при наличии оператора EXCEPT запрос займет часы.

Структура запроса (в упрощенном виде) выглядит следующим образом:

SELECT DISTINCT FIELD_1, FIELD_2, FIELD_3 FROM MYTABLE

EXCEPT

(
SELECT DISTINCT FIELD_1, FIELD_2, FIELD_3 FROM MYTABLE WHERE XXX

UNION

SELECT DISTINCT FIELD_1, FIELD_2, FIELD_3 FROM MYTABLE WHERE YYY
)

Есть ли способы ускорить весь запрос или оператор EXCEPT в целом настолько медленный, что его следует избегать?


person user11034064    schedule 08.02.2019    source источник
comment
В таблицах есть столбцы, а не поля.   -  person jarlh    schedule 08.02.2019
comment
Я бы начал здесь и здесь и здесь. Вы не показали план выполнения, а это необходимо... но поскольку вы используете одну и ту же таблицу во всех трех запросах, я уверен, что есть гораздо более элегантное решение, например NOT EXISTS as @jarlh предложил. Я не понимаю, зачем вам здесь нужны UNION и EXCEPT. Кроме того, DISTINCT не нужен   -  person scsimon    schedule 08.02.2019
comment
Как насчет SELECT DISTINCT FIELD_1, FIELD_2, FIELD_3 FROM MYTABLE WHERE NOT XXX AND NOT YYY?   -  person Peter B    schedule 08.02.2019
comment
Действительно ли все эти три запроса относятся к одной и той же таблице или это просто ваше запутывание?   -  person Tab Alleman    schedule 08.02.2019
comment
В последней статье вы увидите вывод Аарона о том, что это был просто очень многословный способ сообщить вам, что для шаблона поиска всех строк в таблице A, где какое-то условие не существует в таблице B, НЕ СУЩЕСТВУЕТ, как правило, будет вашим лучшим выбором   -  person scsimon    schedule 08.02.2019
comment
Tab: это та же таблица. Первый запрос в основном дает почти всю таблицу, а второй + третий запросы — это подмножества, которые мне нужно убрать из результатов первого запроса.   -  person user11034064    schedule 08.02.2019
comment
Упрощенный фактически означает воображаемый. Никто, кроме вас, точно не знает, что означают XXX и YYY. Мартин сделал важное замечание к одному предложению. Скорее всего, объединение (не объединение всех) и отдельное использование во 2-й части запроса не приносят никакой пользы, и лучше НЕ полагаться на то, что оптимизатор игнорирует попытки удалить дубликаты без каких-либо преимуществ. Вторую часть исключения, вероятно, можно упростить, как предлагает Питер Б. Но все это только догадки.   -  person SMor    schedule 08.02.2019


Ответы (4)


Вы можете сделать это с помощью GROUP BY

SELECT FIELD_1, FIELD_2, FIELD_3 
FROM MYTABLE
GROUP BY FIELD_1, FIELD_2, FIELD_3 
HAVING MAX(CASE WHEN (XXX) OR (YYY) THEN 1 ELSE 0 END) = 0
person Martin Smith    schedule 08.02.2019
comment
хороший ответ. Важно: любой, кто использует этот (или большинство других ответов на этот вопрос), должен обязательно заключить XXX в свой собственный набор скобок, если он представляет составное условие. И, конечно же, то же самое с YYY. - person Richard II; 08.02.2019
comment
Действительно, хорошая мысль. Может случиться так, что это также обеспечит лучшую производительность, чем мое упрощение (v3), зависит от логики XXX или YYY. - person Alexander Volok; 08.02.2019
comment
@RichardII - спасибо. Я добавил их в любом случае, чтобы быть в безопасности. - person Martin Smith; 08.02.2019
comment
@ Ричард II. . . Это будет ужасно, если фильтр использует сложную логику, например (OR + AND). - person Yogesh Sharma; 08.02.2019
comment
@Yogesh, к чему это относится в вашем комментарии? сам запрос? мое предложенное изменение? И что, по-твоему, будет в этом ужасного: производительность? разборчивость? - person Richard II; 08.02.2019

Я бы использовал NOT EXISTS вместо CTE :

WITH CTE AS (
     <your union query>
)
SELECT mt.*
FROM MYTABLE mt
WHERE NOT EXISTS (SELECT 1 FROM CTE c WHERE c.FIELD_1 = mt.FIELD_1 AND . . . );   
person Yogesh Sharma    schedule 08.02.2019

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

Карьер ниже будет иметь гораздо более высокую производительность.

Вот как бы я это сделал.

SELECT DISTINCT FIELD_1, FIELD_2, FIELD_3 FROM MYTABLE
where Id not in (
SELECT Id FROM MYTABLE WHERE XXX and YYY
)

person Alen.Toma    schedule 08.02.2019
comment
OP может не иметь прав, необходимых для добавления нового столбца в таблицу. это тоже не нужно. см. обновление этого ответа: stackoverflow.com/a/54594763/1633949 для более простого подхода. - person Richard II; 08.02.2019
comment
Я вижу, я предположил, что у вас есть первичный ключ в таблице. всегда хорошо иметь первичный ключ в таблице. - person Alen.Toma; 08.02.2019

Иногда полезно упростить задачу оптимизатора запросов, разделив рабочую нагрузку на несколько этапов. Особенно, если выполнение может занять несколько часов:

-- Step 1
SELECT FIELD_1, FIELD_2, FIELD_3 INTO #Step1 FROM
(
SELECT FIELD_1, FIELD_2, FIELD_3 FROM MYTABLE WHERE XXX    
UNION  
SELECT FIELD_1, FIELD_2, FIELD_3 FROM MYTABLE WHERE YYY
) d

-- Step 2:
SELECT DISTINCT FIELD_1, FIELD_2, FIELD_3 FROM MYTABLE    
EXCEPT    
SELECT FIELD_1, FIELD_2, FIELD_3  FROM #Step1

Обратите внимание, что некоторые предложения DISTINCT удалены.


Обновление, версия 3. На основе последнего обновления OP:

Tab: это та же таблица. Первый запрос в основном дает почти всю таблицу, а второй + третий запрос - это подмножества, которые мне нужно убрать из результатов первого запроса.

Я считаю, что весь запрос можно переписать так:

SELECT DISTINCT FIELD_1, FIELD_2, FIELD_3 FROM MYTABLE ext
WHERE NOT EXISTS (

SELECT * FROM (
    SELECT FIELD_1, FIELD_2, FIELD_3 FROM MYTABLE  
    WHERE  ( (XXX) OR (YYY))  -- original filter
)   list 
WHERE
    list.FIELD_1 = ext.FIELD_1
AND list.FIELD_2 = ext.FIELD_2
AND list.FIELD_3 = ext.FIELD_3
) 
person Alexander Volok    schedule 08.02.2019
comment
Вы пропустили FROM в шаге 1 - person scsimon; 08.02.2019
comment
В этом случае используйте CTE в качестве шага 1. - person benjamin moskovits; 08.02.2019
comment
@benjaminmoskovits, CTE не будет изолировать эту часть для собственного исполнения. Поэтому в итоге вся команда SQL будет скомпилирована в один общий план запроса. Идея с временной таблицей заключалась в том, чтобы разделить рабочую нагрузку и упростить задачу оптимизатора для создания эффективного плана запроса. Однако OP дал важный комментарий о том, что все операции выполняются в одной таблице, поэтому я добавил обновление для ответа. - person Alexander Volok; 08.02.2019
comment
Я согласен, если это одна и та же таблица, просто используйте WHERE NOT ( XXX ) AND NOT (YYY) - person kpollock; 08.02.2019
comment
@ Александр, я написал свой ответ после того, как ты написал свой первоначальный пост. После публикации моего я увидел, что вы обновили свой, включив в него ту же концепцию. Сроки. Обещаю, я вас не копировал :-) - person Richard II; 08.02.2019
comment
упрощение недопустимо. Если в группе FIELD_1, FIELD_2, FIELD_3 есть несколько строк, соответствующих и не соответствующих условиям xxx/yyy, они будут возвращены. Запрос в OP исключит группу, если существует одна строка, соответствующая этим условиям. - person Martin Smith; 08.02.2019
comment
@RichardII, да, это было очевидно, когда OP обновил вопрос, но Мартин сделал правильный комментарий, в любом случае я предоставил другую версию упрощения. - person Alexander Volok; 08.02.2019
comment
да, я удаляю свой ответ именно из-за наблюдения @MartinSmith. - person Richard II; 08.02.2019