Осмысляем работу джойнов в SQL: от реляционной алгебры до наглядных картинок
Выбираем, какие фильмы посмотреть, с помощью соединения данных в SQL.
скриншот из игры team fortress 2 / valve
Опять эта проблема — выбрать кино на вечер. Благодаря стриминговым сервисам доступны едва ли не все фильмы мира: это бесконечное полотно с постерами и фильтры, фильтры, фильтры…
МОЗГ: Поставлю-ка я фильтр по стране: пусть будет Дания, и добавлю ограничение по жанру — триллер… Ну вот — другое дело, относительно небольшой список.
— Мозг, а знаешь почему? Да потому что здесь только фильмы, которые сняты в Дании И помечены как триллеры.
— Но если мне нужно не И, а ИЛИ? Или я хочу выбирать из датских фильмов, но только НЕ триллеры? Как выставить такой фильтр?
— Да не знаю я, как задать такие критерии в этом сервисе. Вот если бы можно было писать на SQL — тут бы решение нашлось для любой комбинации признаков.
— Пруфы будут?
— Легко! Ещё и картинки будут. У меня и база фильмов уже спарсена — тренируйся не хочу.
Договоримся об обозначениях
Назовём множество датских фильмов — D, а множество триллеров — T. У каждого фильма будет уникальный номер, он же ключ. Раз ключ — пусть зовётся Key.
Заодно вспомним, как на SQL пишется простой запрос для связывания данных из двух таблиц:
INNER JOIN
Если не уточнить тип соединения (JOIN), то по умолчанию применяется INNER JOIN — как раз тот вариант, который сработал в нашем кинофильтре. Это он выбирает и триллеры, и датские фильмы одновременно.
А вот и обещанная картинка:
При INNER JOIN (внешнее соединение) выбираются только совпадающие по условию объединения данные из обеих таблиц. Порядок таблиц в запросе не важен.
LEFT JOIN
Подойдёт, если:
- я хочу выбрать датские фильмы;
- и согласна, что среди них могут быть триллеры;
- но мне не интересны триллеры производства других стран, только датские.
Вот так будет выглядеть SQL-запрос:
И подобающая случаю диаграмма:
LEFT JOIN (левое внешнее объединение) — то же самое, что LEFT OUTER JOIN.
В результат попадают совпадающие по ключу данные обеих таблиц и все записи из левой таблицы, для которых не нашлось пары в правой.
— А что, если я вообще не фанат триллеров, но датские фильмы мне интересны?
— Тогда к скрипту выше нужно дописать одно условие:
Дословно T.Key IS NULL означает, что нужно включить в результат только записи, в которых значение ключа для записей из множества триллеров пусто.
— Как это пусто? Ведь у фильма не может быть пустой номер!
— Верно. Думай об этом не как о едином множестве фильмов, а как о парах фильмов из двух групп. Мы берём один фильм из первой группы (датские) и ищем во второй группе (триллеров) для него пару — фильм с таким же номером.
Если пара найдётся (значит, попался датский триллер) — считаем, что T.Key не пустой, иначе он как раз и будет IS NULL.
Диаграмма теперь выглядит так:
RIGHT JOIN
Если день не задался и смотреть что-то доброе и вечное настроения нет, можно установить фильтр для отбора только триллеров. И пусть даже среди них будут датские, но вот другие категории датских фильмов рассматривать не будем.
Вот как это выглядит на диаграмме:
— Подожди, ну не настолько же всё плохо — давай хотя бы датские триллеры исключим. Мне кажется, я даже догадываюсь, как это сделать:
— Совершенно верно! Наверняка и диаграмма для этого случая тебя не удивит:
RIGHT JOIN (правое внешнее соединение) — то же самое, что RIGHT OUTER JOIN.
В результат объединения попадают совпадающие по ключу записи обеих таблиц и все данные из правой таблицы, для которых не нашлось пары в левой.
FULL OUTER JOIN
— Что, если фильм нам подойдёт, когда он ИЛИ датский, ИЛИ триллер?
— Тогда и пригодился бы новый тип JOIN:
И вот такая «цельная» у него диаграмма:
FULL JOIN (полное внешнее соединение) — то же самое, что FULL OUTER JOIN.
В результат объединения попадают совпадающие по ключу записи обеих таблиц и все строки из этих двух таблиц, для которых пар не нашлось. Порядок таблиц в запросе не важен.
— А если я хочу ИЛИ датский, ИЛИ триллер, но не одновременно эти два признака, так можно?
— Да, можно и так. Здесь снова пригодится проверка на NULL.
И общий для триллеров и датских фильмов сектор на диаграмме останется незакрашенным:
— Что ж, похоже, мы перебрали все возможные комбинации для связывания двух множеств.
— А вот и нет. Есть ещё один, особенный джойн.
CROSS JOIN
У него и персональное название есть — декартово произведение. Для двух множеств в результате CROSS JOIN получаются все возможные пары, в каждой из которых будет представитель одного и второго множества.
— Стоп! А где же тут соединение по ключу: ON D.Key=T.Key?
— В том-то и дело, что мы составляем пары, не обращая внимания на ключи, просто каждый элемент первой группы сопоставляем с каждым из второй.
— Звучит как-то сложно. Зачем вообще могут понадобиться такие пары? Для фильмов это бессмыслица какая-то получится.
— Пожалуй. Давай возьмём пример ближе к жизни. Предположим, есть магазин одежды, и мы хотим составить для него таблицу размеров одежды, но с учётом её цвета. То есть нужны все возможные комбинации размер + цвет. Это и достигается с помощью CROSS JOIN.
А схематично изобразить это можно вот так:
CROSS JOIN (перекрёстное объединение) возвращает декартово произведение: все возможные комбинации соединения записей из первой и второй таблиц.
— Ок, с джойнами теперь всё ясно. Остался только один вопрос.
— И какой же?
— Смотреть-то что будем? 😜