Соединение таблиц в SQL: короткий гайд с примерами
За 15 минут вы научитесь соединять таблицы через JOIN в PostgreSQL 16: получите совпавшие строки, сохраните записи без пары и избежите частых ошибок с условиями соединения и дубликатами.

За 15 минут вы научитесь соединять таблицы через JOIN в PostgreSQL 16: найдёте совпавшие записи, сохраните строки без пары и проверите результат на небольшом примере. Это пригодится, когда имя клиента хранится отдельно от его заказов, а в отчёте нужны оба значения.
Что понадобится
Пример написан для PostgreSQL 16 и подходит для запуска в psql или редакторе SQL-клиента. Нужна доступная база данных и возможность выполнять запросы. В примере используются временные таблицы: они существуют только в текущем подключении. Если ваш редактор открывает новое подключение для каждого запроса, выполните все блоки в одной сессии.
Термин: ключ — столбец, по которому сопоставляют строки двух таблиц. Здесь это customers.id и orders.customer_id: во второй таблице хранится идентификатор клиента.
JOIN не «склеивает» таблицы навсегда: запрос лишь собирает нужные столбцы и строки для конкретного результата.
Пошагово соединяем таблицы
1. Подготовьте данные
Создайте таблицу клиентов и таблицу заказов, затем добавьте три заказа. У клиента Веры заказов нет — эта запись пригодится для проверки LEFT JOIN. Запустите блок целиком:
CREATE TEMP TABLE customers (
id integer PRIMARY KEY,
name text NOT NULL
);
CREATE TEMP TABLE orders (
id integer PRIMARY KEY,
customer_id integer REFERENCES customers (id),
amount numeric(10, 2) NOT NULL
);
INSERT INTO customers (id, name) VALUES
(1, 'Анна'),
(2, 'Борис'),
(3, 'Вера');
INSERT INTO orders (id, customer_id, amount) VALUES
(101, 1, 1250.00),
(102, 1, 800.00),
(103, 2, 2100.00);
Проверка: выполните SELECT count(*) FROM customers; и SELECT count(*) FROM orders;. Запросы должны вернуть 3 и 3. Если таблица уже существует в вашей сессии, начните новое подключение или удалите временные таблицы и запустите блок снова.
2. Получите только совпавшие строки
INNER JOIN возвращает строки, для которых условие соединения выполняется в обеих таблицах. Укажите условие после ON и задайте короткие псевдонимы таблиц — так запрос легче читать:
SELECT c.name, o.id AS order_id, o.amount
FROM customers AS c
INNER JOIN orders AS o
ON o.customer_id = c.id
ORDER BY o.id;
Проверка: должно получиться три строки. У Анны будут два заказа, у Бориса — один, Веры в результате не будет: для неё нет совпадающей строки в orders. Если убрать INNER, запись JOIN без указания типа тоже означает внутреннее соединение.
3. Сохраните клиентов без заказов
Когда важны все строки из первой таблицы, используйте LEFT JOIN. Он сохраняет каждую строку слева, а столбцы правой таблицы заполняет значением NULL, если пары нет:
SELECT c.name, o.id AS order_id, o.amount
FROM customers AS c
LEFT JOIN orders AS o
ON o.customer_id = c.id
ORDER BY c.id, o.id;
Проверка: вернутся четыре строки. У Веры значения order_id и amount будут NULL, у Анны — две строки. Это ожидаемо: JOIN создаёт отдельную строку результата для каждого совпадения.
4. Не отфильтруйте нужные строки после соединения
Условие в WHERE применяется к уже собранным строкам. Поэтому такой запрос не покажет клиентов без заказов: сравнение NULL > 1000 не даёт истинного результата, и строка отсекается.
SELECT c.name, o.id AS order_id, o.amount
FROM customers AS c
LEFT JOIN orders AS o
ON o.customer_id = c.id
WHERE o.amount > 1000;
Если нужны все клиенты, но к каждому надо присоединить только заказы дороже 1000, перенесите фильтр в условие соединения:
SELECT c.name, o.id AS order_id, o.amount
FROM customers AS c
LEFT JOIN orders AS o
ON o.customer_id = c.id
AND o.amount > 1000
ORDER BY c.id, o.id;
Проверка: в результате должны остаться все три клиента. У Веры и у Анны будет по строке без подходящего заказа; у Бориса — строка с заказом на 2100. Когда фильтр в WHERE нужен намеренно, например чтобы показать только подходящие заказы, оставляйте его там.
5. Разберитесь с повторяющимися строками
Две строки Анны в результате — не ошибка и не обязательно дубликаты исходных данных. У клиента два подходящих заказа, поэтому результат содержит две пары. Если нужен один итог на клиента, посчитайте записи группировкой:
SELECT c.id, c.name, count(o.id) AS order_count
FROM customers AS c
LEFT JOIN orders AS o
ON o.customer_id = c.id
GROUP BY c.id, c.name
ORDER BY c.id;
Проверка: получится три строки, а количество заказов будет 2 у Анны, 1 у Бориса и 0 у Веры. Здесь используется count(o.id), а не count(*): при LEFT JOIN строка клиента без заказа всё равно присутствует, и count(*) посчитает её как одну строку.
Как выбрать тип JOIN
INNER JOIN— нужны только пары, которые нашлись в обеих таблицах.LEFT JOIN— нужны все строки левой таблицы, включая те, для которых пары нет.RIGHT JOINсохраняет все строки правой таблицы. Обычно тот же результат проще выразить, поменяв таблицы местами и использовавLEFT JOIN.FULL JOINсохраняет строки с обеих сторон, даже когда пары нет. Нужен, например, для сопоставления двух наборов данных; в обычном отчёте о клиентах и заказах часто избыточен.
CROSS JOIN образует все возможные пары строк: если слева 10 строк, а справа 20, результат содержит 200 строк. Используйте его только когда действительно нужен такой набор, иначе результат может неожиданно разрастись.
Если запрос не сработал
column reference "id" is ambiguous. В обеих таблицах есть столбецid, и PostgreSQL не знает, какой нужен. Пишитеc.idилиo.id.relation "orders" does not exist. Таблица не создана в текущей базе или временная таблица создана в другом подключении. Выполните подготовительный блок в той же сессии.column o.customer_id does not exist. В таблице нет столбца с таким именем: проверьте схему и написание. УсловиеONдолжно связывать реальные столбцы обеих таблиц.- LEFT JOIN неожиданно не показывает строки без пары. Проверьте, не отсекает ли их условие по правой таблице в
WHERE. Если нужно сохранить такие строки, условие может относиться кON. - Результат содержит больше строк, чем ожидалось. Одной строке слева могут соответствовать несколько строк справа. Проверьте число совпадений по ключу, прежде чем применять
DISTINCT: он скрывает одинаковые строки результата, но не исправляет ошибочное условие соединения.
Что делать дальше
Возьмите реальный запрос из своего отчёта и сначала посчитайте, сколько строк он возвращает с INNER JOIN и с LEFT JOIN. Затем проверьте поля в условии ON и наличие нескольких совпадений на один ключ. Если запрос станет медленным на больших таблицах, изучите план выполнения и индексы отдельно: скорость зависит от схемы, объёма данных и конкретного запроса, поэтому универсального числа для JOIN нет.
Границы этого гайда — соединение таблиц по равенству и базовая фильтрация. Соединение по диапазонам, анализ плана выполнения и подбор индексов — отдельные темы.
IT-архитектор / SQL-оптимизатор: Поиск N+1, оптимизация запросов, code review, поиск багов.
Спросить за 20 ₽Источники: PostgreSQL 16: объединение таблиц, PostgreSQL 16: SELECT
Частые вопросы
Чем JOIN отличается от INNER JOIN?
Как вывести клиентов, у которых нет заказов?
Почему после JOIN стало больше строк?
Можно ли соединять таблицы по нескольким столбцам?
Примеры подойдут для другой версии PostgreSQL?
Что делать, если LEFT JOIN всё равно не показывает строку без пары?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.