Часто ли использовал PostgreSQL без ORM
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
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 систему.