Код
#статьи

Осмысляем работу джойнов в SQL: от реляционной алгебры до наглядных картинок

Выбираем, какие фильмы посмотреть, с помощью соединения данных в SQL.

скриншот из игры team fortress 2 / valve

Опять эта проблема — выбрать кино на вечер. Благодаря стриминговым сервисам доступны едва ли не все фильмы мира: это бесконечное полотно с постерами и фильтры, фильтры, фильтры…

Кусок бесконечного полотна фильмов

МОЗГ: Поставлю-ка я фильтр по стране: пусть будет Дания, и добавлю ограничение по жанру — триллер… Ну вот — другое дело, относительно небольшой список.

— Мозг, а знаешь почему? Да потому что здесь только фильмы, которые сняты в Дании И помечены как триллеры.

 — Но если мне нужно не И, а ИЛИ? Или я хочу выбирать из датских фильмов, но только НЕ триллеры? Как выставить такой фильтр?

— Да не знаю я, как задать такие критерии в этом сервисе. Вот если бы можно было писать на SQL — тут бы решение нашлось для любой комбинации признаков.

— Пруфы будут?

— Легко! Ещё и картинки будут. У меня и база фильмов уже спарсена — тренируйся не хочу.

Договоримся об обозначениях

Назовём множество датских фильмов — D, а множество триллеров — T. У каждого фильма будет уникальный номер, он же ключ. Раз ключ — пусть зовётся Key.

Заодно вспомним, как на SQL пишется простой запрос для связывания данных из двух таблиц:

SELECT * (или список полей)
FROM D
JOIN T ON D.Key=T.Key
WHERE условие отбора

INNER JOIN

Если не уточнить тип соединения (JOIN), то по умолчанию применяется INNER JOIN — как раз тот вариант, который сработал в нашем кинофильтре. Это он выбирает и триллеры, и датские фильмы одновременно.

А вот и обещанная картинка:

Никаких компромиссов — исключительно датские триллеры

При INNER JOIN (внешнее соединение) выбираются только совпадающие по условию объединения данные из обеих таблиц. Порядок таблиц в запросе не важен.

LEFT JOIN

Подойдёт, если:

  • я хочу выбрать датские фильмы;
  • и согласна, что среди них могут быть триллеры;
  • но мне не интересны триллеры производства других стран, только датские.

Вот так будет выглядеть SQL-запрос:

SELECT *
FROM D
LEFT JOIN T ON D.Key=T.Key

И подобающая случаю диаграмма:

Датские любых жанров, а из триллеров только датские

LEFT JOIN (левое внешнее объединение) — то же самое, что LEFT OUTER JOIN.

В результат попадают совпадающие по ключу данные обеих таблиц и все записи из левой таблицы, для которых не нашлось пары в правой.

— А что, если я вообще не фанат триллеров, но датские фильмы мне интересны?

— Тогда к скрипту выше нужно дописать одно условие:

SELECT *
FROM D
LEFT JOIN T ON D.Key=T.Key
WHERE T.Key IS NULL

Дословно T.Key IS NULL означает, что нужно включить в результат только записи, в которых значение ключа для записей из множества триллеров пусто.

 — Как это пусто? Ведь у фильма не может быть пустой номер!

— Верно. Думай об этом не как о едином множестве фильмов, а как о парах фильмов из двух групп. Мы берём один фильм из первой группы (датские) и ищем во второй группе (триллеров) для него пару — фильм с таким же номером.

Если пара найдётся (значит, попался датский триллер) — считаем, что T.Key не пустой, иначе он как раз и будет IS NULL.

Диаграмма теперь выглядит так:

Датские — все, кроме триллеров. Триллеры полностью исключаем

RIGHT JOIN

Если день не задался и смотреть что-то доброе и вечное настроения нет, можно установить фильтр для отбора только триллеров. И пусть даже среди них будут датские, но вот другие категории датских фильмов рассматривать не будем.

SELECT *
FROM D
RIGHT JOIN T ON D.Key=T.Key

Вот как это выглядит на диаграмме:

Триллеры любых стран, а из датских фильмов — только триллеры

 — Подожди, ну не настолько же всё плохо — давай хотя бы датские триллеры исключим. Мне кажется, я даже догадываюсь, как это сделать:

SELECT *
FROM D
RIGHT JOIN T ON D.Key=T.Key
WHERE D.Key IS NULL

— Совершенно верно! Наверняка и диаграмма для этого случая тебя не удивит:

Триллеры, но только не датские. Датских фильмов вообще не нужно.

RIGHT JOIN (правое внешнее соединение) — то же самое, что RIGHT OUTER JOIN.

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

FULL OUTER JOIN

— Что, если фильм нам подойдёт, когда он ИЛИ датский, ИЛИ триллер?

— Тогда и пригодился бы новый тип JOIN:

SELECT *
FROM D
FULL OUTER JOIN T ON D.Key=T.Key

И вот такая «цельная» у него диаграмма:

Фильм может быть любым, если он датский или триллер

FULL JOIN (полное внешнее соединение) — то же самое, что FULL OUTER JOIN.

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

— А если я хочу ИЛИ датский, ИЛИ триллер, но не одновременно эти два признака, так можно?

— Да, можно и так. Здесь снова пригодится проверка на NULL.

SELECT *
FROM D
FULL OUTER JOIN T ON D.Key=T.Key
WHERE D.Key IS NULL OR T.Key IS NULL

И общий для триллеров и датских фильмов сектор на диаграмме останется незакрашенным:

Можно датские, можно триллеры, но исключаем датские триллеры

 — Что ж, похоже, мы перебрали все возможные комбинации для связывания двух множеств.

— А вот и нет. Есть ещё один, особенный джойн.

CROSS JOIN

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

SELECT *
FROM D
CROSS JOIN T 

 — Стоп! А где же тут соединение по ключу: ON D.Key=T.Key?

— В том-то и дело, что мы составляем пары, не обращая внимания на ключи, просто каждый элемент первой группы сопоставляем с каждым из второй.

— Звучит как-то сложно. Зачем вообще могут понадобиться такие пары? Для фильмов это бессмыслица какая-то получится.

— Пожалуй. Давай возьмём пример ближе к жизни. Предположим, есть магазин одежды, и мы хотим составить для него таблицу размеров одежды, но с учётом её цвета. То есть нужны все возможные комбинации размер + цвет. Это и достигается с помощью CROSS JOIN.

А схематично изобразить это можно вот так:

Декартово произведение множеств

CROSS JOIN (перекрёстное объединение) возвращает декартово произведение: все возможные комбинации соединения записей из первой и второй таблиц.

 — Ок, с джойнами теперь всё ясно. Остался только один вопрос.

— И какой же?

 — Смотреть-то что будем? 😜


Изучайте IT на практике — бесплатно

Курсы за 2990 0 р.

Я не знаю, с чего начать
Научитесь: Профессия Python-разработчик Узнать больше
Понравилась статья?
Да

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies 🍪

Ссылка скопирована