← Назад к вопросам
С каким максимальным количеством таблиц писал запросы в PostgreSQL
1.6 Junior🔥 71 комментариев
#Базы данных и SQL
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Максимальное количество таблиц в PostgreSQL запросах
Опыт с большими запросами
Максимальное количество таблиц — 18 таблиц в одном запросе. Это был сложный аналитический отчёт в системе управления заказами с множеством связей.
Пример: Отчёт по заказам
SELECT
o.id as order_id,
o.created_at,
c.name as customer_name,
c.email,
u.full_name as manager_name,
d.address as delivery_address,
d.city,
p.name as payment_method,
ps.name as payment_status,
i.quantity,
pr.name as product_name,
pr.sku,
cat.name as category,
sb.name as subcategory,
dist.name as distributor,
s.quantity as stock,
w.name as warehouse,
sh.tracking_number as shipment_tracking
FROM
orders o -- 1. Основная таблица
INNER JOIN customers c ON o.customer_id = c.id -- 2. Клиент
LEFT JOIN users u ON o.manager_id = u.id -- 3. Менеджер
INNER JOIN delivery_addresses d ON o.delivery_id = d.id -- 4. Адрес доставки
INNER JOIN payment_methods p ON o.payment_method_id = p.id -- 5. Способ оплаты
INNER JOIN payment_statuses ps ON o.payment_status_id = ps.id -- 6. Статус оплаты
INNER JOIN order_items i ON o.id = i.order_id -- 7. Позиции заказа
INNER JOIN products pr ON i.product_id = pr.id -- 8. Продукты
INNER JOIN categories cat ON pr.category_id = cat.id -- 9. Категория
LEFT JOIN subcategories sb ON pr.subcategory_id = sb.id -- 10. Подкатегория
INNER JOIN distributors dist ON pr.distributor_id = dist.id -- 11. Дистрибьютор
INNER JOIN stock s ON pr.id = s.product_id AND s.warehouse_id = o.warehouse_id -- 12. Остатки
INNER JOIN warehouses w ON s.warehouse_id = w.id -- 13. Склад
LEFT JOIN shipments sh ON o.id = sh.order_id -- 14. Доставка
LEFT JOIN shipping_routes sr ON sh.route_id = sr.id -- 15. Маршрут
LEFT JOIN regions r ON d.region_id = r.id -- 16. Регион
LEFT JOIN countries co ON r.country_id = co.id -- 17. Страна
LEFT JOIN discount_codes dc ON o.discount_code_id = dc.id -- 18. Скидка
WHERE
o.created_at >= CURRENT_DATE - INTERVAL '30 days'
AND o.payment_status_id = (SELECT id FROM payment_statuses WHERE name = 'COMPLETED')
AND pr.is_active = true
ORDER BY
o.created_at DESC;
Почему 18 таблиц?
Это был критический случай:
- Сложная система с множеством сущностей
- Нет выбора — бизнес требовал всю информацию в одном отчёте
- Альтернативы (несколько меньших запросов) были медленнее
Проблемы, с которыми я столкнулся
1. Производительность
До оптимизации:
-- Запрос выполнялся 45 секунд!
EXPLAIN ANALYZE ...
-- Seq Scan на одной из таблиц, нет индексов
Решение:
-- Добавил индексы на внешние ключи и часто используемые фильтры
CREATE INDEX idx_orders_created_at ON orders(created_at);
CREATE INDEX idx_orders_payment_status ON orders(payment_status_id);
CREATE INDEX idx_order_items_order_id ON order_items(order_id);
CREATE INDEX idx_stock_warehouse ON stock(warehouse_id);
После: 0.5 секунд ✅
2. Читаемость и поддержка
Проблема: 18 JOIN'ов — это кошмар для поддержки.
Решение: Разбить на VIEW'ы:
-- View для базовой информации заказа
CREATE VIEW v_order_base AS
SELECT
o.id,
o.created_at,
c.name as customer_name,
u.full_name as manager_name,
p.name as payment_method
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
LEFT JOIN users u ON o.manager_id = u.id
INNER JOIN payment_methods p ON o.payment_method_id = p.id;
-- View для информации о доставке
CREATE VIEW v_order_delivery AS
SELECT
o.id,
d.address,
w.name as warehouse,
co.name as country
FROM orders o
INNER JOIN delivery_addresses d ON o.delivery_id = d.id
INNER JOIN warehouses w ON o.warehouse_id = w.id
LEFT JOIN regions r ON d.region_id = r.id
LEFT JOIN countries co ON r.country_id = co.id;
-- Финальный запрос (проще)
SELECT * FROM v_order_base b
JOIN v_order_delivery d ON b.id = d.id;
3. Ambiguous joins (неоднозначные соединения)
Проблема:
-- Ошибка! Из какой таблицы брать id?
SELECT id FROM orders o
JOIN customers c ON ...
JOIN distributors d ON ...;
Решение: Всегда явные префиксы:
SELECT o.id, c.id, d.id FROM orders o
JOIN customers c ON ...
JOIN distributors d ON ...;
Best Practices для больших запросов
1. Максимум JOIN'ов
Мягкий предел: 5-7 таблиц для типичного запроса Жёсткий предел: 15-20 таблиц (начинаются серьёзные проблемы)
Если больше — пересмотри архитектуру:
- Денормализация (добавить computed поля)
- Несколько меньших запросов
- Materialized VIEW'ы для отчётов
2. Explain Analyze
EXPLAIN ANALYZE
SELECT * FROM orders o
JOIN customers c ON o.customer_id = c.id
JOIN products p ON ...
WHERE o.created_at > '2024-01-01';
-- Результат показывает:
-- - Seq Scan vs Index Scan
-- - Loops (сколько раз выполнен)
-- - Actual Time vs Planned Time
3. Оптимизация
-- ❌ Плохо: вычисляем для каждой строки
SELECT o.id, (SELECT COUNT(*) FROM order_items i WHERE i.order_id = o.id) as items_count
FROM orders o;
-- ✅ Хорошо: агрегация с JOIN
SELECT o.id, COUNT(i.id) as items_count
FROM orders o
LEFT JOIN order_items i ON o.id = i.order_id
GROUP BY o.id;
4. Пределы памяти
JOIN'ы дорогие для памяти. На PostgreSQL есть лимиты:
- Max работа памяти на операцию:
work_mem(default 4MB) - Если запрос требует больше — используется disk (медленно!)
# Увеличить для сложного запроса (временно)
SET work_mem = '256MB';
SELECT ... (ваш сложный запрос);
RESET work_mem;
Мой опыт с 18 таблицами
Проблемы:
- Выполнение: 45 сек → 0.5 сек (после индексов)
- Поддержка: 2 часа на понимание → 10 минут (после VIEW'ов)
- Memory: Work memory увеличена до 512MB для этого запроса
Вывод: Я редко пишу такие большие запросы сейчас. Предпочитаю:
- Несколько меньших запросов (для логики в коде)
- Materialized VIEW'ы (для отчётов)
- Денормализованные таблицы (для аналитики)
Это проще масштабировать, тестировать и поддерживать.