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

Часто ли использовал PostgreSQL без ORM

2.2 Middle🔥 201 комментариев
#Node.js и JavaScript#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

PostgreSQL без ORM: Raw SQL опыт

Да, я часто работаю с raw SQL в PostgreSQL, и это важный навык. Я не фанат ORM — они удобны для simple CRUD, но в production системах часто нужен полный контроль над запросами. За 10+ лет я учился когда использовать ORM, а когда писать raw SQL напрямую.

Когда я пишу raw SQL вместо ORM

1. Complex queries с multiple joins и aggregations

Орм генерирует неэффективный SQL, особенно с COUNT и GROUP BY:

// ORM approach (плохо генерирует SQL)
const stats = await db.user.findMany({
  include: {
    posts: {
      include: {
        comments: true,
      },
    },
  },
});

// Raw SQL (controlled и efficient)
const result = await db.query(`
  SELECT 
    u.id,
    u.username,
    COUNT(DISTINCT p.id) as post_count,
    COUNT(DISTINCT c.id) as comment_count,
    AVG(p.views) as avg_post_views
  FROM users u
  LEFT JOIN posts p ON u.id = p.user_id
  LEFT JOIN comments c ON p.id = c.post_id
  GROUP BY u.id
  HAVING COUNT(DISTINCT p.id) > 0
  ORDER BY post_count DESC
`);

2. Bulk operations на огромных датасетах

ОРМ тоже медленно работает с bulk updates:

// ORM (медленно — делает UPDATE для каждого записа)
await db.post.updateMany({
  where: { published: false },
  data: { status: 'archived' },
});

// Raw SQL (один efficient query)
await db.query(`
  UPDATE posts
  SET status = 'archived', updated_at = NOW()
  WHERE published = false
  AND created_at < NOW() - INTERVAL '30 days'
  RETURNING id;
`);

3. Advanced PostgreSQL features (JSON, arrays, window functions)

ОРМ поддерживают их плохо или вообще не поддерживают:

// PostgreSQL JSON functions
const result = await db.query(`
  SELECT 
    id,
    metadata->>'title' as title,
    metadata->'tags' as tags,
    ROW_NUMBER() OVER (ORDER BY created_at DESC) as row_num
  FROM users
  WHERE metadata @> '{"verified": true}'::jsonb
`);

// Array operations
await db.query(`
  UPDATE users
  SET interests = array_append(interests, $1)
  WHERE id = $2
  AND NOT interests @> ARRAY[$1];
`, [newInterest, userId]);

Как я пишу raw SQL безопасно

Параметризованные запросы (обязательно для production)

// ПЛОХО: SQL injection уязвимость!
await db.query(`SELECT * FROM users WHERE id = ${userId}`);

// ХОРОШО: параметры
await db.query('SELECT * FROM users WHERE id = $1', [userId]);

// С Prisma.raw можно тоже
await db.$queryRaw`
  SELECT * FROM users 
  WHERE id = ${userId} 
  AND email = ${email}
`;

Практические примеры из production

Пример 1: Fast pagination без offset (очень большие датасеты)

// Плохо: OFFSET медленно на больших числах
const page = await db.query(`
  SELECT * FROM events 
  ORDER BY id DESC 
  LIMIT 20 OFFSET ${(pageNum - 1) * 20}
`);

// Хорошо: keyset pagination
const page = await db.query(`
  SELECT * FROM events 
  WHERE id < $1
  ORDER BY id DESC 
  LIMIT 20
`, [lastSeenId]);

Второй вариант работает 1000x быстрее на миллиардах записей.

Пример 2: Upsert операция

// ORM: нужно сначала найти, потом create/update
let user = await db.user.findUnique({ where: { email } });
if (user) {
  user = await db.user.update({
    where: { email },
    data: { lastLogin: new Date() },
  });
} else {
  user = await db.user.create({
    data: { email, lastLogin: new Date() },
  });
}

// Raw SQL: один atomic запрос
const result = await db.query(`
  INSERT INTO users (email, last_login)
  VALUES ($1, NOW())
  ON CONFLICT (email) DO UPDATE
  SET last_login = EXCLUDED.last_login
  RETURNING *;
`, [email]);

Пример 3: Полнотекстовый поиск

const results = await db.query(`
  SELECT 
    id,
    title,
    ts_rank(search_vector, query) as rank
  FROM articles,
  plainto_tsquery('english', $1) as query
  WHERE search_vector @@ query
  ORDER BY rank DESC
  LIMIT 10;
`, [searchQuery]);

Пример 4: Window functions для аналитики

// Найти топ комментаторов по месяцам с ранжированием
const stats = await db.query(`
  SELECT 
    user_id,
    DATE_TRUNC('month', created_at) as month,
    COUNT(*) as comment_count,
    RANK() OVER (
      PARTITION BY DATE_TRUNC('month', created_at) 
      ORDER BY COUNT(*) DESC
    ) as monthly_rank,
    PERCENT_RANK() OVER (
      ORDER BY COUNT(*) DESC
    ) as overall_percentile
  FROM comments
  WHERE created_at > NOW() - INTERVAL '12 months'
  GROUP BY user_id, DATE_TRUNC('month', created_at)
  HAVING COUNT(*) > 5
`);

Инструменты для работы с raw SQL

1. pg library — минималистичный, но мощный

import { Pool } from 'pg';

const pool = new Pool({
  host: process.env.DB_HOST,
  port: parseInt(process.env.DB_PORT),
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
});

const result = await pool.query(
  'SELECT * FROM users WHERE id = $1',
  [userId]
);

2. pgvector для работы с embeddings

// Semantic search на PostgreSQL!
const similar = await db.query(`
  SELECT id, content, similarity
  FROM documents
  ORDER BY embedding <-> $1
  LIMIT 5
`, [userEmbedding]);

3. Proper connection pooling

// PgBouncer или встроенный pool важен для production
const pool = new Pool({
  max: 20, // Max connections
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

// Always release connections
const client = await pool.connect();
try {
  await client.query('...');
} finally {
  client.release();
}

Когда я все-таки использую ORM

  • Simple CRUD операции — создание, обновление, удаление пользователя
  • Relations и associations — ORM хорош для fetch related data
  • Type safety — TypeScript интеграция в Prisma
  • Миграции — ORM управляет schema легче чем raw migrations

My production approach

Я использую гибридный подход:

// Prisma для простого CRUD
const user = await db.user.findUnique({ where: { id } });

// Raw SQL для complex queries
const stats = await db.$queryRaw`
  SELECT ... FROM ...
`;

// Лучшее из обоих миров

Это дает flexibility: использую ORM где она помогает, но не ограничиваюсь ей. Raw SQL знание критично для senior-level backend разработчика — это разница между разработчиком который может только click buttons и разработчиком который может оптимизировать production систему.

Часто ли использовал PostgreSQL без ORM | PrepBro