← Назад к вопросам
В чем разница между JOIN в MongoDB и PostgreSQL?
2.2 Middle🔥 81 комментариев
#Базы данных и SQL
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между JOIN в MongoDB и PostgreSQL
Это ключевая разница между SQL и NoSQL подходом. Хотя MongoDB добавил $lookup, это не то же самое что полноценные JOINs в PostgreSQL.
PostgreSQL JOINs (встроенная поддержка)
SQL JOINs — это первоклассные операции, оптимизированные на уровне СУБД.
-- INNER JOIN: вернет только те записи, которые есть в обеих таблицах
SELECT u.name, o.total
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE u.active = true;
-- Результат:
-- name | total
-- Alice | 100
-- Bob | 250
-- LEFT JOIN: все пользователи + их заказы (если есть)
SELECT u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id;
-- Результат:
-- name | order_count
-- Alice | 2
-- Bob | 3
-- John | 0 -- нет заказов, но выводим
-- MULTIPLE JOINs (несколько таблиц одновременно)
SELECT u.name, o.id, p.product_name
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
LEFT JOIN order_items oi ON o.id = oi.order_id
LEFT JOIN products p ON oi.product_id = p.id;
Плюсы:
- Оптимизатор запросов составляет наилучший план исполнения
- Можно joinировать 5-10 таблиц без проблем
- Выполняется атомарно на уровне СУБД
- Индексы используются эффективно
MongoDB $lookup (синтетические JOINs)
MongoDB добавил $lookup в 2015, но это не полноценная замена JOINs.
// MongoDB: $lookup (Stage в aggregation pipeline)
db.users.aggregate([
{
$lookup: {
from: "orders", // Какую таблицу joinировать
localField: "_id", // Поле из users
foreignField: "user_id", // Поле из orders
as: "orders" // Куда положить результат
}
},
{
$match: { active: true } // WHERE условие
},
{
$project: { // SELECT какие поля
name: 1,
"orders.total": 1
}
}
]);
// Результат:
// {
// _id: 1,
// name: "Alice",
// orders: [
// { _id: 101, total: 100 },
// { _id: 102, total: 50 }
// ]
// }
Минусы:
- Выполняется как вложенный цикл в памяти (N * M операций)
- Нет встроенной оптимизации
- Нет использования индексов как в PostgreSQL
- Сложнее писать, больше кода
- Результат раздут (all orders вложены в каждый user)
Прямое сравнение
// ---- POSTGRESQL ----
// Получить всех пользователей с количеством заказов
SELECT u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id;
// FAST и COMPACT: результат один row per user
// name | order_count
// Alice | 2
// Bob | 3
// ---- MONGODB ----
db.users.aggregate([
{ $lookup: { from: "orders", localField: "_id", foreignField: "user_id", as: "orders" } },
{ $project: { name: 1, order_count: { $size: "$orders" } } }
]);
// SLOW и BLOATED: весь массив orders загружается в памяти
// { name: "Alice", order_count: 2, orders: [ {...}, {...} ] }
// { name: "Bob", order_count: 3, orders: [ {...}, {...}, {...} ] }
На примере реального приложения
Задача: получить 100 пользователей с их последними 5 заказами
-- PostgreSQL: элегантно и быстро
WITH user_orders AS (
SELECT
user_id,
id,
total,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) as rn
FROM orders
)
SELECT
u.id,
u.name,
json_agg(json_build_object(
'order_id', uo.id,
'total', uo.total
)) as recent_orders
FROM users u
LEFT JOIN user_orders uo ON u.id = uo.user_id AND uo.rn <= 5
WHERE u.active = true
LIMIT 100
GROUP BY u.id;
-- Результат: 100 rows, каждый row с до 5 заказов
// MongoDB: сложнее, медленнее, раздутые данные
db.users.aggregate([
{ $match: { active: true } },
{ $limit: 100 },
{
$lookup: {
from: "orders",
let: { userId: "$_id" },
pipeline: [
{ $match: { $expr: { $eq: ["$user_id", "$$userId"] } } },
{ $sort: { created_at: -1 } },
{ $limit: 5 }
],
as: "recent_orders"
}
},
{
$project: {
_id: 1,
name: 1,
recent_orders: 1
}
}
]);
// Выполняется медленнее: для каждого из 100 пользователей
// нужно прочитать его заказы
Производительность
PostgreSQL:
- Сложный JOIN с 100K пользователей и 1М заказов: 100-500ms
- Оптимизатор выбирает best execution plan
- Индексы используются эффективно
MongoDB:
- Аналогичный $lookup: 2-5 секунд (10x медленнее)
- Нет оптимизации, N*M цикл в памяти
- Если заказов много, может OOM (out of memory)
В Node.js коде
// ---- TypeORM + PostgreSQL ----
const users = await userRepository
.createQueryBuilder('u')
.leftJoinAndSelect(
'u.orders',
'o',
'o.user_id = u.id'
)
.where('u.active = :active', { active: true })
.loadRelationIds({ relations: ['orders'] })
.limit(100)
.getMany();
// ---- Mongoose + MongoDB ----
const users = await User.aggregate([
{ $match: { active: true } },
{ $limit: 100 },
{
$lookup: {
from: 'orders',
localField: '_id',
foreignField: 'user_id',
as: 'orders'
}
}
]);
Основные различия
| Аспект | PostgreSQL | MongoDB |
|---|---|---|
| Синтаксис | SQL JOIN (стандарт) | $lookup (специфичен) |
| Производительность | 100-500ms | 2-5s (10x медленнее) |
| Оптимизация | Встроена в СУБД | Нет, вручную |
| Индексы | Использует эффективно | Ограниченно |
| Множественные JOINs | 5-10 таблиц OK | Сложно, медленно |
| Результат | Нормализованный | Денормализованный (раздут) |
| Сложность кода | Проще (SQL понятнее) | Сложнее (pipeline) |
| Гарантии | ACID, консистентность | Может быть race condition |
Мой вывод
Это одна из причин почему PostgreSQL более практичен. JOINs — это стержень аналитики, и PostgreSQL их делает эффективно "из коробки". MongoDB $lookup добавляет сложность без выигрыша в производительности.