Умова в межах ПРИЄДНАЙТЕСЬ або ДІЙ


194

Чи є якась різниця (ефективність, найкраща практика тощо) між тим, як ставити умову в пункті ПРИЄДНАЙТЕ порівняно з пунктом 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
@Steve Dignan: Вам слід порівняти це із зразковими даними та переглянути плани запитів. Відповідь буде дуже, дуже зрозумілою. І - бонус - у вас з'явиться фрагмент коду, який ви можете використовувати повторно, коли виникають складніші ситуації.
S.Lott

1
Я особисто поставив би умову в пункті JOIN, якщо умова описує співвідношення. Загальні умови, які просто фільтрують набір результатів, перейдуть до частини WHERE тоді. Напр.FROM Orders JOIN OrderParties ON Orders.Id = OrderParties.Order AND OrderParties.Type = 'Recipient' WHERE Orders.Status = 'Canceled'
Glutexo

Відповіді:


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 2012
Шахдат

2
@Shahdat, якщо ви отримуєте таку істотну різницю в продуктивності, переміщуючи ваші умови фільтра від пункту "де" до внутрішнього з'єднання, вам потрібно розмістити ці плани виконання.
Кейд Ру

4
@Cade Я дослідив плани виконання - обидва сценарії, що показують однакову вартість. Я запускаю запити кілька разів, здається, обидва займають однаковий час. Раніше я виконував запити на виробництві і отримав значну різницю в продуктивності, тому що базу даних використовували живі користувачі. Вибачте за цю плутанину.
Шахдат

4
Ця відповідь підходить для INNER JOIN, але не для лівого / правого приєднання.
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 року, таким чином перетворюючи ліве з'єднання у внутрішнє з'єднання.

Другий видасть ці записи плюс будь-яким клієнтам без замовлень. Набір результатів сильно відрізняється залежно від того, де ви ставите умову. (Select * призначений лише для цілей, звичайно, ви не повинні використовувати це у виробничому коді.)

Виняток із цього випадку, коли ви хочете бачити лише записи в одній таблиці, але не в іншій. Тоді ви використовуєте пункт де для умови, а не приєднання.

SELECT *
FROM dbo.Customers AS CUS 
LEFT JOIN dbo.Orders AS ORD 
ON CUS.CustomerID = ORD.CustomerID
WHERE ORD.OrderID is null

Дякуємо за пояснення з прикладами
Відмовитись від Йосифа

1
"перетворюючи таким чином ліве з'єднання у внутрішнє з'єднання". Як? Ви можете трохи допрацювати?
користувач1451111

@ user1451111 Дізнайтеся, що повертається ВЛІТЬ / ПРАВО ПРИЄДНУЙТЕСЬ: РОБОТИ ВНУТРІШНЬОГО ПРИЄДНАННЯ плюс незрівняні ліві / праві рядки таблиці, розширені NULL. FULL JOIN повертає INNER JOIN рядки UNION ВСІ незрівняні ліві та праві рядки таблиці, розширені NULLs. Завжди знайте, що ВНУТРІШНІ ПРИЄДНАЙТЕ, як Ви хочете, як частина ВИХІДНОГО ПРИЄДНАННЯ. WHERE або ON, для якого потрібен розширений стовпчик NULL не повинен бути NULL після того, як OUTER JOIN ON буде видалено будь-які рядки, розширені NULLs, тобто залишає лише рядки INNER JOIN, тобто "перетворює OUTER JOIN у ВНУТРІШНУ ПРИЄДНАННЯ".
philipxy

1
@ user1451111 або, простіше кажучи: A left join Bце кожен рядок з A, приєднаний до кожного відповідного рядка з B. Якщо в B немає рядка, який відповідає, то стовпці A мають значення, але кожен стовпець із B у цьому рядку показує як NULL значення. Якщо ви написали, where B.somecolumn = ‘somevalue’то у вас значення NULL (B.кольова колонка) порівнюється з "деяким значенням". Все, що порівнюється з NULL, є помилковим, тому всі ваші рядки, де немає відповідного ряду B для рядка A, усуваються, а результати, які ви отримуєте, такі самі, як і ВНУТРІШНЬОГО ПРИЄДНАННЯ, отже, зовнішнє з'єднання стало внутрішнім
Caius Jard

так, я перевірив, що результати однакові для: SELECT funds.id, prospects.id fundsВнутрішні перспективи приєднання на (prospects.id = funds.lead_id та prospects.is_manual = 'ні') та SELECT funds.id, prospects.id ВІД fundsліворуч приєднатися до перспектив (prospects.id = funds.lead_id), де prospects.is_manual = 'ні'
Rohit Dhiman

25

Більшість продуктів RDBMS оптимізують обидва запити однаково. У "Налаштування продуктивності SQL" Петра Гулуцана та Труді Пелзера вони протестували декілька марок RDBMS і не виявили різниці в продуктивності.

Я вважаю за краще тримати умови приєднання окремо від умов обмеження запиту.

Якщо ви використовуєте OUTER JOINіноді, необхідно додати умови до пункту приєднання.


1
Я погоджуюся з вами, що синтаксично це чистіше, і я повинен відкласти ваші знання про цю книгу та вашу дуже високу репутацію, але я можу придумати 4 запити за останній тиждень із дуже різними планами виконання, часом процесора та логічним записом, коли Я перемістився там, де предикати на приєднання.
marr75

2
Ви запитували про кращі практики. Як тільки ви перевірите, як працює конкретна реалізація RDBMS, інші люди дали правильну пораду: орієнтир.
Білл Карвін

12

ДЕ буде фільтруватися після того, як ПРИЄДНАЄТЬСЯ.

Фільтр на ПРИЄДНАЙТЕСЬ, щоб запобігти додаванню рядків під час процесу ПРИЄДНАННЯ.


10
Семантично їх запобігають під час процесу INNER JOIN, але оптимізатор може переставити INNER JOIN та WHERE предикати за бажанням, тому оптимізатор може виключати їх пізніше, якщо захоче.
Кейд Ру

1
Кейд Ру: Правильно. Часто те, що ви пишете в SQL, - це не те, що дає вам оптимізатор, коли все буде сказано і зроблено. Тоді я б припустив, що це було б правильно у світі загальної теорії, тоді як ваша відповідь, безумовно, більш правильна у світі оптимізаторів автоматичних запитів :)
TheTXI

Мені подобається це пояснення стану вON
Роберт Роша

3

Я вважаю за краще ПРИЄДНАЙТЕСЬ приєднуватися до повних таблиць / переглядів, а потім використовувати WHERE, щоб ввести предикат отриманого набору.

Він відчуває себе синтаксично чистіше.


2

Я зазвичай бачу підвищення продуктивності під час фільтрації під час з'єднання. Особливо, якщо ви можете приєднатись до індексованих стовпців для обох таблиць. Ви повинні мати можливість скоротити логічні зчитування з більшістю запитів, які також роблять це, що в умовах великої гучності значно кращий показник продуктивності, ніж час виконання.

Мене завжди приємно розвеселяє, коли хтось показує SQL тестування, і вони виконали обидві версії проростка 50 000 разів опівночі на сервері розробників і порівняли середній час.


0

Поставити умову приєднання здається мені "семантично неправильним", оскільки це не те, що "ПРИЄДНАЙТЕСЬ". Але це дуже якісно.

Додаткова проблема: якщо ви вирішили перейти від внутрішнього з'єднання до, скажімо, правильного з'єднання, умова бути всередині ПРИЄДНАЙТЕ може призвести до несподіваних результатів.


3
Іноді ці результати начебто "очікувані", а іноді навіть "навмисні" (наприклад, із зовнішніми приєднаннями, де умова WHERE має іншу семантику, ніж умова JOIN).
Марсель Тот

0

На мою думку, приєднання швидше, коли у вас більша таблиця. Це насправді не така велика різниця, хоча особливо, якщо ви маєте справу зі значно меншою таблицею. Коли я вперше дізнався про приєднання, мені сказали, що умови приєднання так само, як і умови клаузулювання, і що я міг би їх використовувати взаємозамінно, якщо в пункті де було конкретно стосовно того, на якій таблиці робити умову.


-4

Краще додати умову в Join. Продуктивність важливіша, ніж читабельність. Для великих наборів даних це важливо.


1
Чи є у вас якісь докази, дослідження, як розміщення згаданих предикатів впливає на результативність?
Зсо
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.