25
Тайны оператора JOIN Проблемы и их решение

Тайны оператора JOIN

Embed Size (px)

DESCRIPTION

by Oleksandr Sinitsyn

Citation preview

Page 1: Тайны оператора JOIN

Тайны оператора JOINПроблемы и их решение

Page 2: Тайны оператора JOIN

Содержание

• Что такое оператор соединения?

• CROSS JOIN

• INNER JOIN

• OUTER JOIN

• Множественные соединения

• Вопросы-ответы

Page 3: Тайны оператора JOIN

Оператор соединения

- оператор алгебры, две таблицы на входе, таблица на выходе

- результат широкий, т.к. содержит все колонки исходных таблиц

- соединение происходит по условию

- количество строк в результате меняется

Page 4: Тайны оператора 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

Page 5: Тайны оператора JOIN

Пример, INNER JOIN

SELECT *

FROM Users

JOIN Payments

ON Users.UserID = Payments.UserID

Page 6: Тайны оператора JOIN

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)}

Опасная штука, т.к. может создавать результаты огромных размеров.

Page 7: Тайны оператора JOIN

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

Page 8: Тайны оператора JOIN

INNER JOIN

Общее правило - в ON мы пишем условия для соединения таблиц, в WHERE - все остальные условия

SELECT * FROM Users JOIN Payments

ON Users.UserID = Payments.UserID

WHERE Users.UserID = 1

Page 9: Тайны оператора JOIN

OUTER JOIN

Необходимость таких соединений становится понятна если попытаться написать классический запрос вида "вернуть всех пользователей без платежей".

Внешнее соединение возвращает все строки из одной таблицы, плюс добавляет строки из другой таблицы, если выполняется условие соединения. В результате у нас как минимум столько же строк как в одной таблице. Есть LEFT JOIN, RIGHT JOIN и FULL JOIN. LEFT JOIN - все строки из левой таблицы,RIGHT JOIN - все строки из правой, FULL - все строки из обоих таблиц.

Page 10: Тайны оператора JOIN

Пример, 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

Page 11: Тайны оператора JOIN

Пример, OUTER JOIN

SELECT Users.*

FROM Users

LEFT JOIN Payments

ON Users.UserID = Payments.UserID

WHERE

Payments.UserID IS NULL

Page 12: Тайны оператора JOIN

OUTER JOIN, трюк 1

• Есть разница, где писать условия, в WHERE или ON. То, что написано в ON "выполнится" до соединения, в WHERE - после. Поэтому проверку на IS NULL можно написать только в WHERE, в этот момент NULL уже есть в результате соединения.

WHERE

Payments.UserID IS NULL

Page 13: Тайны оператора JOIN

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

Page 14: Тайны оператора JOIN

OUTER JOIN, трюк 3

• Условие к левой таблице в ON будет проигнорировано, таков алгоритм LEFT JOIN, он всегда вернет все записи из левой таблицы

FROM Users LEFT JOIN Payments

ON Users.UserID = Payments.UserID

and Users.UserID = 1

Page 15: Тайны оператора JOIN

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'

Page 16: Тайны оператора JOIN

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

Page 17: Тайны оператора JOIN

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

Page 18: Тайны оператора JOIN

Множественные соединения

Три и более таблицы во FROM.

Правило 1: INNER JOIN безопасны с точки зрения логики.

Порядок соединения определяется оптимизатором, записывать можно в произвольном порядке.

A JOIN B JOIN C = C JOIN B JOIN A

Page 19: Тайны оператора JOIN

Множественные соединения

Правило 2: OUTER JOIN коварны

Обрабатываются в порядке записи.

Классическая задача - вернуть данные из зависимых таблиц для всех пользователей. Интуитивное решение:

FROM Users

LEFT JOIN Payments

ON Users.UserID = Payments.UserID

JOIN PaymentDetail

ON Payments.PaymentID = ...

Page 20: Тайны оператора JOIN

Множественные соединения

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

Сервер знает об этом, и обрабатывает такое соединение как INNER JOIN.

Page 21: Тайны оператора JOIN

Множественные соединения

Интуитивное решение:

FROM Users

LEFT JOIN Payments

ON Users.UserID = Payments.UserID

LEFT JOIN PaymentDetail

ON Payments.PaymentID = ...

Работает, но не эффективно, и неправильно, если есть платежи без деталей. Мы хотели информацию по всем пользователям независимо от наличия платежей, платежи без деталей нам не интересны.

Page 22: Тайны оператора JOIN

Множественные соединения

Можно поставить OUTER JOIN последним оператором:

FROM Payments

JOIN PaymentDetail

ON Payments.PaymentID = ...

RIGHT JOIN Users

ON Users.UserID = Payments.UserID

Если запрос сложный, то это может быть непросто. Более наглядно использовать скобки.

Page 23: Тайны оператора JOIN

Множественные соединения

FROM Users

LEFT JOIN

(Payments JOIN PaymentDetail

ON Payments.PaymentID = ...

)

ON Users.UserID = Payments.UserID

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

Page 24: Тайны оператора JOIN

Множественные соединения

FROM Users

LEFT JOIN Payments

JOIN PaymentDetail

ON Payments.PaymentID = ...

ON Users.UserID = Payments.UserID

Порядок записи ON не произвольный, это chiatric relation. Первый - последний, второй - предпоследний, третий - третий с конца и т.д.

Page 25: Тайны оператора JOIN

Вопросы - ответы

Рекомендуемая литература:

Itzik Ben-Gan

Inside Microsoft SQL Server 2008: T-SQL Querying