← Назад к вопросам

В чем разница между 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'
    }
  }
]);

Основные различия

АспектPostgreSQLMongoDB
СинтаксисSQL JOIN (стандарт)$lookup (специфичен)
Производительность100-500ms2-5s (10x медленнее)
ОптимизацияВстроена в СУБДНет, вручную
ИндексыИспользует эффективноОграниченно
Множественные JOINs5-10 таблиц OKСложно, медленно
РезультатНормализованныйДенормализованный (раздут)
Сложность кодаПроще (SQL понятнее)Сложнее (pipeline)
ГарантииACID, консистентностьМожет быть race condition

Мой вывод

Это одна из причин почему PostgreSQL более практичен. JOINs — это стержень аналитики, и PostgreSQL их делает эффективно "из коробки". MongoDB $lookup добавляет сложность без выигрыша в производительности.

В чем разница между JOIN в MongoDB и PostgreSQL? | PrepBro