Внутреннее соединение таблиц: экви- и неэкви-условия, множественные условия
INNER JOIN — это самый распространённый тип соединения, который возвращает только те строки, для которых есть совпадение в обеих таблицах.
SELECT <колонки>
FROM таблица1
INNER JOIN таблица2 ON таблица1.колонка = таблица2.колонка;Ключевое слово INNER можно опустить — JOIN по умолчанию означает INNER JOIN:
SELECT <колонки>
FROM таблица1
JOIN таблица2 ON таблица1.колонка = таблица2.колонка;Используем те же таблицы, что и в предыдущей теме:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
product VARCHAR(100),
amount DECIMAL(10, 2)
);
INSERT INTO users (name, email) VALUES
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com'),
('Charlie', 'charlie@example.com'),
('Diana', 'diana@example.com');
INSERT INTO orders (user_id, product, amount) VALUES
(1, 'Laptop', 1200.00),
(1, 'Mouse', 50.00),
(2, 'Keyboard', 150.00),
(5, 'Monitor', 300.00); -- заказ для несуществующего пользователя!Данные:
users: orders:
+----+---------+----------------+----+---------+----------+--------+
| id | name | email | id | user_id | product | amount |
+----+---------+----------------+----+---------+----------+--------+
| 1 | Alice | alice@... | 1 | 1 | Laptop | 1200.00|
| 2 | Bob | bob@... | 2 | 1 | Mouse | 50.00 |
| 3 | Charlie | charlie@... | 3 | 2 | Keyboard | 150.00 |
| 4 | Diana | diana@... | 4 | 5 | Monitor | 300.00 |
+----+---------+----------------+----+---------+----------+--------+
SELECT u.name, o.product, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id;Результат:
+-------+----------+--------+
| name | product | amount |
+-------+----------+--------+
| Alice | Laptop | 1200.00|
| Alice | Mouse | 50.00 |
| Bob | Keyboard | 150.00 |
+-------+----------+--------+
Что произошло:
user_id = 5 не попал — пользователя с id = 5 не существуетНаиболее распространённая форма INNER JOIN — эквисоединение, где условие использует оператор равенства (=):
-- Эквисоединение по первичному и внешнему ключу
FROM users
JOIN orders ON users.id = orders.user_idЭто наиболее эффективный тип соединения, особенно если обе колонки проиндексированы.
INNER JOIN может использовать любые операторы сравнения, не только =:
-- Диапазонное соединение
SELECT e.name, s.grade, s.min_salary, s.max_salary
FROM employees e
JOIN salary_grades s ON e.salary BETWEEN s.min_salary AND s.max_salary;-- Соединение по неравенству
SELECT a.id, b.id
FROM table_a a
JOIN table_b b ON a.value > b.value;Можно указывать несколько условий в ON, соединяя их через AND:
-- Соединение по составному ключу
SELECT *
FROM order_items oi
JOIN products p ON oi.product_id = p.id AND oi.warehouse_id = p.warehouse_id;-- Дополнительные условия фильтрации в ON
SELECT u.name, o.product
FROM users u
JOIN orders o ON u.id = o.user_id AND o.amount > 100;Важно: Условия в
ONприменяются до соединения таблиц. Условия вWHERE— после. Для INNER JOIN разница не критична, но для OUTER JOIN это существенно.
INNER JOIN позволяет соединять множество таблиц:
SELECT
u.name AS user_name,
o.id AS order_id,
p.name AS product_name,
c.name AS category_name
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN products p ON o.product_id = p.id
JOIN categories c ON p.category_id = c.id;Порядок таблиц в INNER JOIN не важен — оптимизатор PostgreSQL сам выберет оптимальный план.
INNER JOIN часто используется с агрегатными функциями:
-- Количество заказов и общая сумма по каждому пользователю
SELECT
u.name,
COUNT(o.id) AS order_count,
SUM(o.amount) AS total_amount
FROM users u
JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name;Результат:
+-------+-------------+--------------+
| name | order_count | total_amount |
+-------+-------------+--------------+
| Alice | 2 | 1250.00 |
| Bob | 1 | 150.00 |
+-------+-------------+--------------+
Часто комбинируют JOIN с WHERE для дополнительной фильтрации:
-- Только заказы дороже $100
SELECT u.name, o.product, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.amount > 100;-- Только пользователи с именем на 'A'
SELECT u.name, o.product
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.name LIKE 'A%';Для эффективной работы INNER JOIN важно:
Индексы на колонках соединения:
CREATE INDEX idx_orders_user_id ON orders(user_id);Статистика актуальна:
ANALYZE users;
ANALYZE orders;Используйте EXPLAIN для анализа плана:
EXPLAIN ANALYZE
SELECT * FROM users u
JOIN orders o ON u.id = o.user_id;-- ОШИБКА: декартово произведение!
SELECT * FROM users, orders;
-- Правильно:
SELECT * FROM users JOIN orders ON users.id = orders.user_id;-- Плохо: неявное приведение типов
SELECT * FROM users u
JOIN orders o ON u.id = o.user_id::TEXT;
-- Хорошо: типы совпадают
SELECT * FROM users u
JOIN orders o ON u.id = o.user_id;-- Медленно: нет индекса на email
SELECT * FROM users u
JOIN orders o ON u.email = o.customer_email;
-- Быстро: индекс на foreign key
SELECT * FROM users u
JOIN orders o ON u.id = o.user_id;| Сценарий | Подходит |
|---|---|
| Нужны только строки с совпадениями в обеих таблицах | ✅ Да |
| Нужно найти пользователей без заказов | ❌ Нет (используйте LEFT JOIN + WHERE NULL) |
| Нужно показать всех пользователей с их заказами | ❌ Нет (используйте LEFT JOIN) |
| Агрегация только по имеющимся данным | ✅ Да |
INNER JOIN возвращает только строки с совпадениями в обеих таблицахJOIN без уточнения означает INNER JOIN=) — наиболее распространённый и эффективный типON через ANDВопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.