Пошук уроків, статей та іншого контенту
Найпоширеніша плутанина в SQL-запитах: чому LEFT JOIN іноді повертає рядки з NULL, а INNER JOIN їх мовчки прибирає.
У реляційній базі даних (стаття «Індекси в PostgreSQL» уже торкається реляційної моделі) дані свідомо розбиті на кілька таблиць, пов'язаних через ключі — наприклад, users і orders, де orders.user_id посилається на users.id. JOIN об'єднує рядки з кількох таблиць в один результат на основі умови зв'язку — без нього довелось би робити окремий запит на кожен зв'язок і зшивати результати в коді застосунку (саме до цього і зводиться N+1 Query Problem — окрема стаття цієї платформи).
-- users
-- id | name
-- 1 | Оля
-- 2 | Богдан
-- 3 | Ірина (без жодного замовлення)
-- orders
-- id | user_id | total
-- 1 | 1 | 500
-- 2 | 1 | 300
-- 3 | 2 | 150SELECT users.name, orders.total
FROM users
INNER JOIN orders ON orders.user_id = users.id;
-- Оля | 500
-- Оля | 300
-- Богдан | 150
-- Ірина у результаті відсутня — у неї немає жодного рядка в ordersINNER JOIN (найчастіше просто JOIN без уточнення) повертає лише ті рядки, для яких знайшлась відповідність в обох таблицях. Користувачі без жодного замовлення повністю зникають із результату.
SELECT users.name, orders.total
FROM users
LEFT JOIN orders ON orders.user_id = users.id;
-- Оля | 500
-- Оля | 300
-- Богдан | 150
-- Ірина | NULL -- рядок є, але orders.total = NULL, бо збігу не знайшлосьLEFT JOIN гарантує, що кожен рядок лівої таблиці (users) потрапить у результат хоча б один раз — навіть якщо для нього немає жодного відповідного рядка в правій таблиці (orders). Поля з правої таблиці тоді заповнюються NULL.
RIGHT JOIN — те саме, що LEFT JOIN, але з протилежним пріоритетом: гарантує всі рядки правої таблиці. На практиці RIGHT JOIN використовують рідко — той самий результат завжди можна отримати LEFT JOIN, просто поміняти місцями таблиці в FROM/JOIN, а більшість команд дотримується конвенції «завжди LEFT», щоб не тримати в голові два дзеркальні напрямки одночасно.
PostgreSQL підтримує й FULL OUTER JOIN — повертає всі рядки з обох таблиць, заповнюючи NULL там, де збігу немає з будь-якого боку. Використовується рідше за INNER/LEFT, здебільшого для звірки даних із двох джерел («що є в одній таблиці, але відсутнє в іншій, і навпаки»).
INNER JOIN — «покажи лише те, де є зв'язок в обох таблицях» (замовлення разом із іменем покупця — замовлення без покупця не має сенсу).
LEFT JOIN — «покажи все з основної таблиці, а деталі — якщо є» (усіх користувачів, навіть тих, хто ще нічого не замовив).
RIGHT JOIN — рідко потрібен окремо; зазвичай простіше переписати як LEFT JOIN, поміняти таблиці місцями.
FULL OUTER JOIN — звірка двох наборів даних, коли важливі розбіжності в обидва боки.
Використовувати INNER JOIN там, де потрібен LEFT — рядки без зв'язку мовчки зникають із результату, і це часто виявляють лише тоді, коли хтось помічає, що «користувачів у звіті менше, ніж у таблиці users».
Фільтрувати результат LEFT JOIN умовою WHERE orders.total > 100 замість перенесення умови в саму ON-умову з'єднання — WHERE після LEFT JOIN відкидає рядки з NULL (users без замовлень), фактично перетворюючи LEFT JOIN назад на INNER JOIN.
Забувати, що JOIN на невіндексованому полі (orders.user_id без індексу) змушує базу даних порівнювати кожен рядок однієї таблиці з кожним рядком іншої — на великих таблицях це критично повільно; докладніше в статті «Індекси в PostgreSQL».
INNER JOIN повертає лише рядки зі збігом в обох таблицях, LEFT JOIN гарантує всі рядки лівої таблиці (заповнюючи відсутні деталі NULL), RIGHT JOIN — дзеркальна, рідше вживана версія LEFT. Найчастіша практична помилка — випадково перетворити LEFT JOIN назад на INNER, відфільтрувавши NULL-рядки умовою WHERE замість того, щоб включити умову у сам JOIN.