Условие в СОЕДИНЕНИИ или ГДЕ

194

Есть ли разница (производительность, лучшие практики и т. Д.) Между помещением условия в предложение JOIN и предложением WHERE?

Например...

-- Condition in JOIN
SELECT *
FROM dbo.Customers AS CUS
INNER JOIN dbo.Orders AS ORD 
ON CUS.CustomerID = ORD.CustomerID
AND CUS.FirstName = 'John'

-- Condition in WHERE
SELECT *
FROM dbo.Customers AS CUS
INNER JOIN dbo.Orders AS ORD 
ON CUS.CustomerID = ORD.CustomerID
WHERE CUS.FirstName = 'John'

Что вы предпочитаете (и, возможно, почему)?

Стив Диньян
источник
4
Вы выполнили два запроса? Вы проверяли планы выполнения, сгенерированные двумя запросами? Что вы наблюдали?
С.Лотт
22
@ S.Lott, этот запрос только для примера. Мне просто интересно "в общем", какой метод предпочтительнее - если таковой имеется.
Стив Диньян
1
@ Стив Диньян: Вы должны сравнить это с примерами данных и посмотреть на планы запросов. Ответ будет очень и очень ясным. И - бонус - у вас будет фрагмент кода, который вы сможете использовать повторно, когда возникают более сложные ситуации.
С.Лотт
1
Я бы лично поместил условие в предложение JOIN, если условие описывает отношение. Общие условия, которые просто фильтруют результирующий набор, переходят к части WHERE. НапримерFROM Orders JOIN OrderParties ON Orders.Id = OrderParties.Order AND OrderParties.Type = 'Recipient' WHERE Orders.Status = 'Canceled'
Глютексо

Ответы:

154

Реляционная алгебра допускает взаимозаменяемость предикатов в WHEREпредложении и INNER JOIN, таким образом, даже INNER JOINзапросы с WHEREпредложениями могут иметь предикаты, переупорядоченные оптимизатором, чтобы они уже могли быть исключены во время JOINпроцесса.

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

Иногда это включает в себя создание INNER JOINотносительно «неполных» и включение некоторых критериев WHEREпросто для того, чтобы сделать списки критериев фильтрации более легкими в обслуживании.

Например, вместо:

SELECT *
FROM Customers c
INNER JOIN CustomerAccounts ca
    ON ca.CustomerID = c.CustomerID
    AND c.State = 'NY'
INNER JOIN Accounts a
    ON ca.AccountID = a.AccountID
    AND a.Status = 1

Напишите:

SELECT *
FROM Customers c
INNER JOIN CustomerAccounts ca
    ON ca.CustomerID = c.CustomerID
INNER JOIN Accounts a
    ON ca.AccountID = a.AccountID
WHERE c.State = 'NY'
    AND a.Status = 1

Но это зависит, конечно.

Кейд Ру
источник
7
Речь идет не только о чистых запросах или удобочитаемости, но и о производительности. помещение условий в соединение улучшает производительность для большого объема данных с помощью правильно проиндексированных таблиц.
Шахдат,
1
Я просто составляю ежемесячные отчеты о продажах, объединяя 5-6 таблиц в несколько миллионов записей. Perf улучшается на 30% - sql server 2012
Шахдат
2
@Shahdat, если вы получаете значительную разницу в производительности, перемещая условия фильтра из условия where во внутреннее объединение, вы должны опубликовать эти планы выполнения.
Кейд Ру
4
@Cade Я изучил планы выполнения - оба сценария показывают одинаковую стоимость. Я запускаю запросы несколько раз, кажется, что они занимают одно и то же время. Ранее я выполнял запросы на производстве и получил значительную разницу в производительности, поскольку база данных использовалась живыми пользователями. Извините за эту путаницу.
Шахдат
4
Этот ответ подходит для ВНУТРЕННИХ СОЕДИНЕНИЙ, но не для левых / правых.
SOTN
123

Для внутренних объединений я не заметил заметной разницы (но, как и во всех настройках производительности, вы должны проверить свою базу данных в ваших условиях).

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

SELECT *
FROM dbo.Customers AS CUS 
LEFT JOIN dbo.Orders AS ORD 
ON CUS.CustomerID = ORD.CustomerID
WHERE ORD.OrderDate >'20090515'

SELECT *
FROM dbo.Customers AS CUS 
LEFT JOIN dbo.Orders AS ORD 
ON CUS.CustomerID = ORD.CustomerID
AND ORD.OrderDate >'20090515'

Первый даст вам только те записи, чей заказ датирован позднее 15 мая 2009 года, и, таким образом, преобразование левого объединения во внутреннее объединение.

Второй даст эти записи плюс любые клиенты без заказов. Набор результатов сильно отличается в зависимости от того, где вы поставили условие. (Выберите * только для примера, конечно, вы не должны использовать это в рабочем коде.)

Исключением является случай, когда вы хотите видеть только записи в одной таблице, но не в другой. Затем вы используете условие where для условия, а не для соединения.

SELECT *
FROM dbo.Customers AS CUS 
LEFT JOIN dbo.Orders AS ORD 
ON CUS.CustomerID = ORD.CustomerID
WHERE ORD.OrderID is null
HLGEM
источник
Спасибо за объяснение с примерами
Ренни Джозеф
1
«таким образом, преобразовывая левое соединение во внутреннее соединение». Как? Можете ли вы уточнить немного?
user1451111
@ user1451111 Узнайте, что возвращает LEFT / RIGHT JOIN: строки INNER JOIN плюс несопоставленные строки таблицы слева / справа, расширенные значениями NULL. FULL JOIN возвращает строки INNER JOIN UNION ВСЕ несоответствующие левые и правые строки таблицы, расширенные значениями NULL. Всегда знайте, какое ВНУТРЕННЕЕ СОЕДИНЕНИЕ вы хотите, как часть ВНЕШНЕГО СОЕДИНЕНИЯ. WHERE или ON, которые требуют, чтобы столбец, возможно расширенный NULL, не был NULL после того, как OUTER JOIN ON удаляет все строки, расширенные NULL, т.е. оставляет только строки INNER JOIN, т.е. «превращает OUTER JOIN в INNER JOIN».
Philipxy
1
@ user1451111 или, проще говоря: A left join Bкаждая строка из A соединяется с каждой соответствующей строкой из B. Если B не имеет строки, которая соответствует, то столбцы A имеют значение, но каждый столбец из B в этой строке отображается как значения NULL. Если вы написали, у where B.somecolumn = ‘somevalue’вас есть NULL (B.somecolumn), который сравнивается с 'somevalue'. Все, что сравнивается с NULL, является ложным, поэтому все ваши строки, в которых нет соответствующей строки B для строки A, исключаются, и результаты, которые вы получаете, совпадают с результатами INNER JOIN, поэтому внешнее соединение стало внутренним.
Caius Jard
да, я проверил, что результаты одинаковы для: SELECT funds.id, sources.id FROM fundsвнутренних объединений перспективы на (ances.id = funds.lead_id и investors.is_manual = 'нет') и SELECT funds.id, projects.id ОТ fundsслева присоединиться к представителям перспективы (progress.id = funds.lead_id), где success.is_manual = 'нет'
Рохит
25

Большинство продуктов RDBMS оптимизируют оба запроса одинаково. В «Настройка производительности SQL» Питера Гулутзана и Труди Пельцер они протестировали несколько брендов RDBMS и не обнаружили различий в производительности.

Я предпочитаю хранить условия соединения отдельно от условий ограничения запросов.

Если вы используете OUTER JOINиногда, необходимо поместить условия в предложение join.

Билл Карвин
источник
1
Я согласен с вами в том, что синтаксически это чище, и я вынужден полагаться на ваше знание этой книги и вашу очень высокую репутацию, но я могу вспомнить 4 запроса за последнюю неделю с очень разными планами выполнения, временем ЦП и логическим чтением, когда Я перешел, где предикаты для объединения.
marr75
2
Вы спрашивали о лучших практиках. Как только вы приступите к тестированию того, как работает конкретная реализация СУБД, другие люди дали правильный совет: бенчмарк.
Билл Карвин
12

WHERE будет фильтровать после того, как произошло СОЕДИНЕНИЕ.

Фильтруйте JOIN, чтобы предотвратить добавление строк во время процесса JOIN.

TheTXI
источник
10
Семантически они предотвращаются во время процесса INNER JOIN, но оптимизатор может по желанию переставлять предикаты INNER JOIN и WHERE, поэтому оптимизатор может исключить их позже, если пожелает.
Cade Roux
1
Кейд Ру: Верно. Часто то, что вы пишете на SQL, не то, что оптимизатор даст вам, когда все будет сказано и сделано. Тогда я предположил бы, что это будет правильно в мире теории, в то время как ваш ответ, конечно, более корректен в мире автоматических оптимизаторов запросов :)
TheTXI
Мне нравится это объяснение состояния вON
Роберт Роча
3

Я предпочитаю, чтобы JOIN объединял полные таблицы / представления, а затем использовал WHERE для представления предиката результирующего набора.

Это чувствует себя синтаксически чище.

Джонно Нолан
источник
2

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

Я всегда слегка удивлен, когда кто-то показывает свой сравнительный анализ SQL, и он выполнил обе версии sproc 50000 раз в полночь на сервере разработчиков и сравнил среднее время.

marr75
источник
0

Помещение условия в объединение кажется мне «семантически неправильным», поскольку это не то, для чего «соединения» являются «для». Но это очень качественно.

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

Джейкоб Б
источник
3
Иногда эти результаты являются «ожидаемыми», а иногда даже «преднамеренными» (например, с внешними объединениями, где условие WHERE имеет семантику, отличную от условия JOIN).
Марсель Тот
0

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

Эрик
источник
-4

Лучше добавить условие в Join. Производительность важнее, чем удобочитаемость. Для больших наборов данных это имеет значение.

Джеено Шибу
источник
1
Есть ли у вас какие-то доказательства, исследуйте, как размещение упомянутых предикатов влияет на производительность?
Zso