Когда люди говорят о SQL JOIN, они часто используют диаграммы Венна, чтобы проиллюстрировать включение и исключение двух объединенных наборов:
Хотя эти диаграммы Венна, безусловно, полезны для понимания (и запоминания) синтаксиса SQL JOIN, они не совсем точны, поскольку SQL JOIN — это особый тип декартового произведения, CROSS JOIN.
В декартовом произведении между двумя наборами A и B результатом является умножение каждого множества, означающее, что каждый элемент a ∈ A
объединяется с каждым элементом b ∈ B
образуя набор кортежей (a, b)
.
Обычные соединения SQL делают именно это. Когда вы присоединитесь BOOK к AUTHOR, вы, вероятно, получите комбинацию каждого author ∈ AUTHOR
с каждой book ∈ BOOK
, так что для каждой комбинации (author, book)
автор фактически написал книгу.
Истинный смысл диаграмм Венна
Истинный смысл диаграмм Венна гораздо лучше описывается операциями
-
UNION
-
INTERSECT
-
EXCEPT
(илиMINUS
в Oracle)
В следующих разделах мы увидим, что эти операции точно соответствуют семантике операций, которая может быть проиллюстрирована диаграммами Венна, даже если вы сможете «злоупотреблять» операциями JOIN
для достижения того же результата.
UNION
Операция UNION
является наиболее известной среди этих операций с множествами. Это часто также называют «объединением» двух наборов кортежей, где результатом является объединение набора B
с набором A
В следующем примере мы увидим, что нас могут заинтересовать разные люди из нашей базы данных, учитывая их имена и фамилии, независимо от того, являются ли они customer
или staff
:
Исходные диаграммы Венна использовали FULL OUTER JOIN
для моделирования «одной и той же» концепции, хотя эти две вещи не являются строго одинаковыми. Рассмотрим следующий запрос, который мы запустим для базы данных Sakila :
1
2
3
4
5
6
|
SELECT first_name, last_name FROM customer UNION SELECT first_name, last_name FROM staff ORDER BY 1, 2 |
Результат выглядит так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
first_name last_name ------------------------------------ AARON SELBY ADAM GOOCH ADRIAN CLARY AGNES BISHOP ALAN KAHN ALBERT CROUSE ALBERTO HENNING ALEX GRESHAM ALEXANDER FENNELL ALFRED CASILLAS ALFREDO MCADAMS ALICE STEWART ALICIA MILLS ... |
Теперь запустите следующий «эквивалентный» запрос:
1
2
3
4
5
|
SELECT first_name, last_name FROM customer FULL OUTER JOIN staff USING (first_name, last_name) ORDER BY 1, 2 |
Результат снова даст:
01
02
03
04
05
06
07
08
09
10
|
first_name last_name ------------------------------------ AARON SELBY ADAM GOOCH ADRIAN CLARY AGNES BISHOP ALAN KAHN ALBERT CROUSE ALBERTO HENNING ... |
Это работает только потому, что мы используем предложение USING
, которое изначально поддерживается не каждой базой данных. Если бы мы сделали JOIN с более часто используемым предложением ON, нам пришлось бы написать более утомительное:
1
2
3
4
5
6
7
8
|
SELECT COALESCE (c.first_name, s.first_name) AS first_name, COALESCE (c.last_name, s.last_name) AS last_name FROM customer c FULL OUTER JOIN staff s ON (c.first_name, c.last_name) = (s.first_name, s.last_name) ORDER BY 1, 2 |
В этом случае большинство людей, вероятно, по умолчанию уже используют UNION
, поскольку это гораздо более известная операция, чем FULL OUTER JOIN
.
Все поддерживаемые в настоящее время СУБД jOOQ поддерживают UNION
и UNION ALL
(последняя не удаляет дубликаты).
Далее мы увидим, что эквивалентные сравнения могут быть сделаны с другими операциями над множествами:
ПЕРЕСЕЧЕНИЕ
Операция INTERSECT
действительно полезна, когда вы хотите сохранить только те кортежи, которые присутствуют в обоих наборах, которые объединены с использованием INTERSECT
:
Как видите, мы можем пожелать сохранить только тех клиентов, которые также являются действующими лицами. Давайте запустим этот запрос:
1
2
3
4
5
|
SELECT first_name, last_name FROM customer INTERSECT SELECT first_name, last_name FROM actor |
1
2
3
|
first_name last_name ------------------------------------ JENNIFER DAVIS |
Один из наших клиентов также актер. Тот же самый запрос мог быть написан с INNER JOIN
как таковой:
1
2
3
4
|
SELECT first_name, last_name FROM customer INNER JOIN actor USING (first_name, last_name) |
… или с синтаксисом ON
1
2
3
4
5
|
SELECT c.first_name, c.last_name FROM customer c INNER JOIN actor a ON (c.first_name, c.last_name) = (a.first_name, a.last_name) |
На этот раз COALESCE
не требуется, поскольку INNER JOIN
сохраняет только те кортежи из декартового произведения, которые присутствуют на «обеих сторонах» JOIN
, поэтому мы можем выбрать любую из таблиц для префикса наших столбцов.
Вы можете даже решить использовать полусоединение вместо этого, что даст те же результаты:
1
2
3
4
5
6
|
SELECT first_name, last_name FROM customer WHERE (first_name, last_name) IN ( SELECT first_name, last_name FROM actor ) |
или, используя более подробный, но эквивалентный предикат EXISTS
:
1
2
3
4
5
6
7
8
|
SELECT first_name, last_name FROM customer c WHERE EXISTS ( SELECT 1 FROM actor a WHERE (c.first_name, c.last_name) = (a.first_name, a.last_name) ) |
Все вышеперечисленное, опять же, дает:
1
2
3
|
first_name last_name ------------------------------------ JENNIFER DAVIS |
КРОМЕ
Операция EXCEPT
полезна, когда вы хотите сохранить только те кортежи, которые присутствуют в одном наборе, но не в другом:
Выполнение этого запроса:
1
2
3
4
5
6
|
SELECT first_name, last_name FROM customer EXCEPT SELECT first_name, last_name FROM staff ORDER BY 1, 2 |
… даст:
01
02
03
04
05
06
07
08
09
10
|
first_name last_name ------------------------------------ AARON SELBY ADAM GOOCH ADRIAN CLARY AGNES BISHOP ALAN KAHN ALBERT CROUSE ALBERTO HENNING ... |
Согласно исходным диаграммам Венна, это можно настроить с помощью LEFT JOIN
и предиката IS NULL
:
1
2
3
4
5
6
|
SELECT first_name, last_name FROM customer LEFT JOIN staff USING (first_name, last_name) WHERE staff_id IS NULL ORDER BY 1, 2 |
или с предложением ON
:
1
2
3
4
5
6
7
|
SELECT c.first_name, c.last_name FROM customer c LEFT JOIN staff s ON (c.first_name, c.last_name) = (s.first_name, s.last_name) WHERE staff_id IS NULL ORDER BY 1, 2 |
Это совершенно нечитаемо и не говорит о том, что мы удаляем кортежи из набора CUSTOMER
, учитывая их присутствие в другом наборе STAFF
.
Эквивалентная версия, использующая анти-объединение, может быть более читабельной ( хотя NULLs
внимание на NULLs
в предикатах NOT IN
! ):
1
2
3
4
5
6
7
|
SELECT c.first_name, c.last_name FROM customer c WHERE (first_name, last_name) NOT IN ( SELECT first_name, last_name FROM staff ) ORDER BY 1, 2 |
… или, используя NOT EXISTS
:
1
2
3
4
5
6
7
8
9
|
SELECT c.first_name, c.last_name FROM customer c WHERE NOT EXISTS ( SELECT 1 FROM staff s WHERE (c.first_name, c.last_name) = (s.first_name, s.last_name) ) ORDER BY 1, 2 |
Вывод
UNION
, INTERSECT
и EXCEPT
— это очень простые, но очень полезные операции, которые могут время от времени приносить большую пользу в ваших ежедневных задачах SQL. Хотя операции JOIN
гораздо более универсальны, они также более сложны для простых задач, которые могут быть решены с помощью UNION
, INTERSECT
и EXCEPT
Ссылка: | Возможно, вы не используете SQL INTERSECT или EXCEPT, часто достаточно от нашего партнера по JCG Лукаса Эдера в блоге JAVA, SQL и AND JOOQ . |