Основы реляционных баз данных
Теория: Соединения (JOINS)
Реляционная модель подразумевает связь между данными разных отношений посредством внешних ключей. С практической точки зрения это можно сформулировать так — зная первичный ключ одной сущности, мы можем извлечь связанные с ней данные из другой сущности.
В простых ситуациях данные извлекаются так:
Но есть множество ситуаций, где простой выборкой не обойтись. Для этого нужна операция JOIN, которую мы изучим в этом уроке.
JOIN
Для примера попробуем найти всех пользователей Хекслета, которые ни разу не создавали топики. На текущий момент мы знаем ровно один способ выполнить эту задачу. Нужно выполнить два шага:
-
Извлечь из базы всех пользователей, которые создали хотя бы один топик:
-
Найти всех пользователей, у которых идентификаторы не совпадают со списком
user_id, полученном на предыдущем этапе:
Задача будет решена, но есть одна проблема. Идентификаторов может быть очень много. Гонять такое количество записей из базы в код и обратно — не самая разумная идея.
INNER JOIN
Теперь рассмотрим следующую задачу — найти записи о пользователях в одной таблице, для которых нет записей о топиках в другой таблице.
Реляционная алгебра позволяет выполнить эту операцию с помощью соединения JOIN, используя ровно один запрос. Начнем знакомство с JOIN на таком примере, в котором мы найдем имена всех пользователей, создавших хотя бы один топик
Результатом такого запроса станет выборка, в которую попали поля обеих таблиц. Здесь соединяются две таблицы: users и topics по условию users.id = topics.user_id. Это важное условие для правильной работы.
В нашем примере отношения связаны внешним ключом: соответственно, при объединении этих таблиц нужно явно указать, как мы их соединяем. Общий синтаксис выглядит так:
На самом деле общая форма сложнее, потому что объединять можно произвольное число таблиц. Другими словами, условий соединения может быть много.
JOIN — это сокращенная версия соединения INNER JOIN, то есть внутреннего соединения.
В эту выборку попадают только те записи, для которых есть соответствие в другой таблице. Причем, если у одного пользователя пять топиков, то в выборке окажутся все пять строк. Такой запрос имеет смысл делать на странице вывода топиков, что позволит к каждому топику сразу же вывести нужную информацию и о самом пользователе.
Запросы с соединениями порождают одну небольшую проблему. В примере выше часть SELECT содержит только те поля, имена которых уникальны среди всех полей обеих таблиц. Соответственно, при выборке не возникает неоднозначностей.
Если выполнить этот же запрос со звездочкой, то в выборку попадут поля, у которых одинаковые названия, что создаст сложности при анализе данных уже в коде приложения. А при выполнении запроса с указанием дублирующихся полей вообще возникнет ошибка:
В таких случаях спасают псевдонимы и возможность указывать таблицу для каждого поля:
LEFT JOIN
Пока мы все еще не можем решить нашу исходную задачу. Для этого понадобится операция левого соединения LEFT JOIN:
LEFT JOIN берет все данные из одной таблицы и присоединяет к ним данные из другой, если они присутствуют. Если нет, то заполняет их NULL. Чисто технически этот запрос отличается только тем, что добавляется слово LEFT:
LEFT JOIN полезен, когда нам нужно работать со всеми данными одной таблицы и связанными с ними записями, если они есть. Если их нет, то ничего страшного, мы все равно хотим получить данные из первой таблицы.
Этот запрос все еще не возвращает нам то, что мы хотели изначально — записи о пользователях, которые не оставили ни одного топика на Хекслете. Чтобы закончить решение, нужно добавить в выборку условие WHERE:
Запросы на соединение могут быть как очень простыми, так и очень сложными. Они могут занимать несколько экранов текста и включать в себя сразу множество таблиц. В этом уроке мы лишь немного затронули эту тему и познакомились с самой концепцией, остальное познается во время экспериментов в рабочих и тестовых проектах.