Часть 6 из 16

У нас уже есть события, площадки и запросы, которые выводят вычисленные поля. Но афиша должна ответить и на главный вопрос: кто где выступает? Артист может участвовать в разных событиях, а в одном событии бывает несколько слотов, поэтому простого поля artist в таблице events недостаточно.

Создадим таблицу выступлений-связку и соберём первое цельное расписание через JOIN. По ходу разберём, почему в результате повторяются названия событий и когда вместо обычного JOIN нужен LEFT JOIN.

Таблицы артистов и выступлений

CREATE TABLE artists (
 artist_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
 stage_name VARCHAR(120) NOT NULL,
 genre VARCHAR(60) NOT NULL,
 PRIMARY KEY (artist_id), UNIQUE KEY uq_artist_name(stage_name)
) ENGINE=InnoDB;
CREATE TABLE performances (
 performance_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
 event_id BIGINT UNSIGNED NOT NULL,
 artist_id BIGINT UNSIGNED NOT NULL,
 stage_label VARCHAR(80) NOT NULL,
 starts_at DATETIME NOT NULL,
 ends_at DATETIME NOT NULL,
 PRIMARY KEY (performance_id),
 CHECK (ends_at > starts_at),
 FOREIGN KEY (event_id) REFERENCES events(event_id),
 FOREIGN KEY (artist_id) REFERENCES artists(artist_id)
) ENGINE=InnoDB;

Каждая строка performances — один слот. Для тренировки добавьте артистов через INSERT и используйте реальные ID событий и артистов.

Собираем расписание JOIN

SELECT e.name AS event_name, v.name AS venue,
 p.starts_at, p.stage_label, a.stage_name AS artist
FROM performances AS p
JOIN events AS e ON e.event_id=p.event_id
JOIN venues AS v ON v.venue_id=e.venue_id
JOIN artists AS a ON a.artist_id=p.artist_id
ORDER BY p.starts_at;

ON указывает, по каким ключам связываются строки. Псевдонимы сокращают запись и помогают различить одноимённые столбцы.

Когда выбрать LEFT JOIN

INNER JOIN оставляет только строки с парой. Если нужно показать и события без назначенных артистов, начните с events и используйте LEFT JOIN performances. Поля отсутствующего слота станут NULL. Далее добавим продажи и агрегаты.

Как JOIN собирает одну строку результата

Представьте строки с ID: событие 1 ссылается на площадку 1, а слот 1 ссылается на событие 1 и артиста 2. Условие e.event_id = p.event_id заставляет MySQL сопоставить эти записи. Затем второе соединение находит название площадки, третье — сценическое имя артиста.

JOIN не склеивает таблицы навсегда: он формирует результат только на время запроса.

В запросе есть два столбца с названием name, поэтому мы используем алиасы event_name и venue. Префикс таблицы в e.name устраняет неоднозначность. Алиасы таблиц e, p, v и a — короткие имена, действующие в запросе.

Почему иногда строка исчезает

Обычный JOIN показывает только строки с совпадением по ключу. Если событие ещё без артистов, оно не появится в запросе, который начинается с performances. Для списка всех событий начните с events и сделайте LEFT JOIN performances: событие останется, а колонки слота будут NULL.

Практика: удалите из тестовой базы одного слота только после сохранения копии и сравните INNER и LEFT JOIN. Затем восстановите слот. В следующей части будем суммировать данные и проверим, как JOIN может случайно увеличить выручку.

Учимся диагностировать JOIN

Если имя артиста показывается у чужого события, проверьте условие ON: оба ключа должны сравниваться с соответствующей таблицей. Если запрос вернул слишком много строк, проверьте кардинальность связи: одно событие закономерно имеет несколько слотов, значит название события повторится — по одной строке на каждый слот.

Повтор здесь не ошибка, если запрос предназначен показать расписание.

Добавляйте таблицы по одной. Сначала выполните JOIN событий и площадок; проверьте ID и названия. Потом добавьте performances, затем artists. Так легче определить, на каком шаге появляются неожиданные строки.

JOIN и NULL в условиях

При LEFT JOIN отсутствующая сторона получает NULL. Если сразу написать WHERE p.starts_at > ..., строки без выступлений отсеются, потому что сравнение с NULL не истинно; результат фактически станет похож на INNER JOIN.

Если нужно сохранить пустые события и отфильтровать только совпавшие выступления, иногда условие надо перенести в ON. Это тонкое, но важное различие при построении полного расписания.

Для проверки временно выведите ключи обеих таблиц: e.event_id и p.performance_id. Видя их рядом, проще понять, почему конкретная строка появилась.

JOIN или LEFT JOIN?

Вид соединения Что попадёт в результат Пример задачи
JOIN Только строки с найденной парой Показать слоты с артистами
LEFT JOIN Все строки левой таблицы, даже без пары Показать события без артистов

Перед соединением решите, нужно ли сохранить записи без пары. Это требование определяет тип JOIN и помогает понять, почему часть событий исчезла из афиши.

← Предыдущая статья: Вычисления и даты в SQL: готовим расписание фестиваля

Следующая статья: GROUP BY и SUM в MySQL: считаем продажи билетов →