Тайны оператора join
DESCRIPTION
by Oleksandr SinitsynTRANSCRIPT
Тайны оператора JOINПроблемы и их решение
Содержание
• Что такое оператор соединения?
• CROSS JOIN
• INNER JOIN
• OUTER JOIN
• Множественные соединения
• Вопросы-ответы
Оператор соединения
- оператор алгебры, две таблицы на входе, таблица на выходе
- результат широкий, т.к. содержит все колонки исходных таблиц
- соединение происходит по условию
- количество строк в результате меняется
Пример, INNER JOIN
Users Payments
UserID UserName
1 Ivan
2 Pit
3 John
UserID Date Amount
1 01.01.2013 1000
1 01.02.2013 2000
2 01.01.2013 1000
UserID UserName UserID Date Amount
1 Ivan 1 01.01.2013 1000
1 Ivan 1 01.01.2013 2000
2 Pit 2 01.01.2013 1000
Пример, INNER JOIN
SELECT *
FROM Users
JOIN Payments
ON Users.UserID = Payments.UserID
CROSS JOIN
Вырожденый случай соединения, без условия. То же самое, что декартово перемножение.
A = {1, 2}
B = {A, B, C}
A CROSS JOIN B
A x B = {(1,A), (1,B), (1, C), (2,A), (2,B), (2, C)}
Опасная штука, т.к. может создавать результаты огромных размеров.
INNER JOIN
Выводит совпадающие по заданому условию строки из двух таблиц. Условия можно писать в WHERE и в ON, нет практической разницы где это делать.
SELECT * FROM Users JOIN Payments
ON Users.UserID = Payments.UserID
WHERE Users.UserID = 1
SELECT * FROM Users JOIN Payments
ON Users.UserID = Payments.UserID
AND Users.UserID = 1
INNER JOIN
Общее правило - в ON мы пишем условия для соединения таблиц, в WHERE - все остальные условия
SELECT * FROM Users JOIN Payments
ON Users.UserID = Payments.UserID
WHERE Users.UserID = 1
OUTER JOIN
Необходимость таких соединений становится понятна если попытаться написать классический запрос вида "вернуть всех пользователей без платежей".
Внешнее соединение возвращает все строки из одной таблицы, плюс добавляет строки из другой таблицы, если выполняется условие соединения. В результате у нас как минимум столько же строк как в одной таблице. Есть LEFT JOIN, RIGHT JOIN и FULL JOIN. LEFT JOIN - все строки из левой таблицы,RIGHT JOIN - все строки из правой, FULL - все строки из обоих таблиц.
Пример, OUTER JOIN
Users Payments
UserID UserName
1 Ivan
2 Pit
3 John
UserID Date Amount
1 01.01.2013 1000
1 01.02.2013 2000
2 01.01.2013 1000
UserID UserName UserID Date Amount
1 Ivan 1 01.01.2013 1000
1 Ivan 1 01.01.2013 2000
2 Pit 2 01.01.2013 1000
3 John NULL NULL NULL
Пример, OUTER JOIN
SELECT Users.*
FROM Users
LEFT JOIN Payments
ON Users.UserID = Payments.UserID
WHERE
Payments.UserID IS NULL
OUTER JOIN, трюк 1
• Есть разница, где писать условия, в WHERE или ON. То, что написано в ON "выполнится" до соединения, в WHERE - после. Поэтому проверку на IS NULL можно написать только в WHERE, в этот момент NULL уже есть в результате соединения.
WHERE
Payments.UserID IS NULL
OUTER JOIN, трюк 2
• Часто нужно сначала отфильтровать правую таблицу (для LEFT JOIN), а потом соединять. В этом случае условие можно записать в ON.
SELECT Users.*
FROM Users LEFT JOIN Payments
ON Users.UserID = Payments.UserID
and Payments.Date = '2013-01-10'
WHERE
Payments.UserID IS NULL
OUTER JOIN, трюк 3
• Условие к левой таблице в ON будет проигнорировано, таков алгоритм LEFT JOIN, он всегда вернет все записи из левой таблицы
FROM Users LEFT JOIN Payments
ON Users.UserID = Payments.UserID
and Users.UserID = 1
OUTER JOIN, трюк 4.1
• Если используется LEFT JOIN и проверка на IS NULL, то условия в WHERE не должны конфликтовать. Сервер ничего не вычисляя возвращает NULL в этом случае:
SELECT Users.*
FROM Users
LEFT JOIN Payments
ON Users.UserID = Payments.UserID
WHERE
Payments.UserID IS NULL
and Payments.Date = '2013-01-10'
OUTER JOIN, трюк 4.2
• Менее очевидный случай. Запрос работает так, словно условия UserID IS NULL
нет вообще.
FROM Users LEFT JOIN Payments
ON Users.UserID = Payments.UserID
WHERE
(Payments.UserID IS NULL
or Payments.Date = '2013-01-10')
and Payments.Amount > 1000
OUTER JOIN, трюк 4.2
Так и получается, если упростить логическое выражение
(Payments.UserID IS NULL
or Payments.Date = '2013-01-10')
and Payments.Amount > 1000
A = Payments.UserID IS NULLB = Payments.Date = '2013-01-10C = Payments.Amount > 1000
(A + B)*C = A*C + B*CA*C = Payments.UserID IS NULL and Payments.Amount > 1000 == FALSE
Множественные соединения
Три и более таблицы во FROM.
Правило 1: INNER JOIN безопасны с точки зрения логики.
Порядок соединения определяется оптимизатором, записывать можно в произвольном порядке.
A JOIN B JOIN C = C JOIN B JOIN A
Множественные соединения
Правило 2: OUTER JOIN коварны
Обрабатываются в порядке записи.
Классическая задача - вернуть данные из зависимых таблиц для всех пользователей. Интуитивное решение:
FROM Users
LEFT JOIN Payments
ON Users.UserID = Payments.UserID
JOIN PaymentDetail
ON Payments.PaymentID = ...
Множественные соединения
В результате не будет пользователей, у которых нет платежей, т.к. соединения выполняются попарно, после первого соединения они будут в результате, после второго - исчезнут, т.к. у них все поля из Payments - NULL, в том числе те, по которым присоединяется третья таблица.
Сервер знает об этом, и обрабатывает такое соединение как INNER JOIN.
Множественные соединения
Интуитивное решение:
FROM Users
LEFT JOIN Payments
ON Users.UserID = Payments.UserID
LEFT JOIN PaymentDetail
ON Payments.PaymentID = ...
Работает, но не эффективно, и неправильно, если есть платежи без деталей. Мы хотели информацию по всем пользователям независимо от наличия платежей, платежи без деталей нам не интересны.
Множественные соединения
Можно поставить OUTER JOIN последним оператором:
FROM Payments
JOIN PaymentDetail
ON Payments.PaymentID = ...
RIGHT JOIN Users
ON Users.UserID = Payments.UserID
Если запрос сложный, то это может быть непросто. Более наглядно использовать скобки.
Множественные соединения
FROM Users
LEFT JOIN
(Payments JOIN PaymentDetail
ON Payments.PaymentID = ...
)
ON Users.UserID = Payments.UserID
Скобки в такой записи не обязательны, они нужны только для облегчения понимания логики запроса. На самом деле важен только порядок, в котором записаны ON
Множественные соединения
FROM Users
LEFT JOIN Payments
JOIN PaymentDetail
ON Payments.PaymentID = ...
ON Users.UserID = Payments.UserID
Порядок записи ON не произвольный, это chiatric relation. Первый - последний, второй - предпоследний, третий - третий с конца и т.д.
Вопросы - ответы
Рекомендуемая литература:
Itzik Ben-Gan
Inside Microsoft SQL Server 2008: T-SQL Querying