Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Когда использовать сырой SQL
Raw SQL - мощный инструмент, но его нужно использовать осмотрительно. Рассмотрю когда это оправдано.
Почему нужен Raw SQL
ORM (SQLAlchemy, Django ORM) отлично для 80% случаев:
# ORM - простой CRUD
user = User.objects.get(id=123)
user.email = 'new@example.com'
user.save()
posts = Post.objects.filter(user=user).order_by('-created_at')[:10]
Но ORM может быть медленнее:
# ❌ Проблема N+1
users = User.objects.all()[:1000]
for user in users: # +1000 дополнительных запросов!
print(user.posts.count())
# ✓ Raw SQL решает это
result = db.execute("""
SELECT u.*, COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
GROUP BY u.id
LIMIT 1000
""")
Случаи когда нужен Raw SQL
1. Сложные аналитические запросы
# Сложный report с множественными JOIN
query = """
SELECT
DATE_TRUNC('month', p.created_at) as month,
u.country,
COUNT(DISTINCT u.id) as unique_users,
COUNT(p.id) as total_posts,
AVG(p.views) as avg_views,
SUM(CASE WHEN p.status = 'published' THEN 1 ELSE 0 END) as published_count
FROM users u
INNER JOIN posts p ON u.id = p.user_id
LEFT JOIN comments c ON p.id = c.post_id
WHERE u.created_at > NOW() - INTERVAL '1 year'
AND p.status IN ('published', 'draft')
GROUP BY DATE_TRUNC('month', p.created_at), u.country
HAVING COUNT(DISTINCT u.id) > 10
ORDER BY month DESC, unique_users DESC
"""
results = db.execute(query).fetchall()
Почему ORM не справляется:
- Множественные JOIN и GROUP BY
- Window functions (ROW_NUMBER, RANK)
- CASE statements
- Вложенные подзапросы
- Специальные функции СУБД
2. Performance-critical операции
# Массовый импорт 1M записей
# ❌ ORM - очень медленно
for row in csv_data:
User.objects.create(
name=row['name'],
email=row['email']
)
# Это 1M отдельных INSERT запросов
# ✓ Raw SQL - в 100x быстрее
from sqlalchemy import insert
values = [
{'name': row['name'], 'email': row['email']}
for row in csv_data
]
db.execute(insert(User), values)
# Или прямой COPY (PostgreSQL)
with open('data.csv', 'r') as f:
cursor.copy_expert(
"COPY users (name, email) FROM STDIN WITH CSV",
f
)
3. Специфичные для СУБД функции
# PostgreSQL fulltext search
# ❌ Трудно делать через ORM
# ✓ Просто в SQL
query = """
SELECT *,
ts_rank(textsearch, query) as rank
FROM documents,
to_tsquery('russian', :search_query) query,
to_tsvector('russian', content) textsearch
WHERE textsearch @@ query
ORDER BY rank DESC
"""
results = db.execute(query, {'search_query': 'python django'}).fetchall()
# JSON операции (PostgreSQL)
query = """
SELECT id, data->'user'->>'name' as user_name,
data->'metadata'->>'created_date' as created
FROM events
WHERE data @> '{"type": "login"}'
"""
# Window functions
query = """
SELECT *,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at) as row_num,
LAG(amount) OVER (PARTITION BY user_id ORDER BY created_at) as prev_amount
FROM transactions
"""
4. Лучше контролировать JOIN
# ORM может создать неправильный JOIN
# ❌ ORM (может быть неоптимально)
users_with_posts = (
User.objects
.filter(posts__status='published')
.distinct() # Нужно для избежания дублей
)
# Может создать Cartesian product
# ✓ Raw SQL (точный контроль)
query = """
SELECT DISTINCT u.* FROM users u
INNER JOIN posts p ON u.id = p.user_id
WHERE p.status = 'published'
"""
users = db.execute(query).fetchall()
5. Транзакции и блокировки
# Сложная бизнес-логика с гарантиями
from sqlalchemy import text
with db.begin():
# SELECT FOR UPDATE - заблокировать строки
user = db.execute(
text("SELECT * FROM users WHERE id = :id FOR UPDATE"),
{'id': user_id}
).first()
if user['balance'] >= amount:
db.execute(
text("UPDATE users SET balance = balance - :amount WHERE id = :id"),
{'amount': amount, 'id': user_id}
)
db.execute(
text("INSERT INTO transactions (user_id, amount) VALUES (:user_id, :amount)"),
{'user_id': user_id, 'amount': amount}
)
else:
raise InsufficientBalance()
# Автоматический COMMIT если успех, ROLLBACK если ошибка
6. Миграции и schema changes
# Alembic (для Django - South)
from alembic import op
import sqlalchemy as sa
def upgrade():
# Raw SQL для специальных операций
op.execute("""
CREATE INDEX CONCURRENTLY idx_posts_user_id
ON posts(user_id)
WHERE status = 'published'
""")
# Заполнение данных
op.execute("""
UPDATE posts
SET slug = lower(replace(title, ' ', '-'))
WHERE slug IS NULL
""")
Когда НЕ использовать Raw SQL
# ❌ НЕ используй для простого CRUD
# ❌ ORM всегда проще и безопаснее
# Плохо
query = "SELECT * FROM users WHERE id = ?"
user = db.execute(query, (user_id,)).fetchone()
# Хорошо
user = session.query(User).filter(User.id == user_id).first()
# ❌ НЕ используй если не знаешь SQL хорошо
# Риск SQL injection, неправильные JOIN
# ❌ НЕ используй для кода, который может менять СУБД
# PostgreSQL != MySQL != SQLite синтаксис
Правило и best practices
class RawSQLDecisions:
def rule_1_use_orm(self):
"""Начни с ORM, потом оптимизируй"""
# ✓ Хорошо
users = session.query(User).all()
# Если медленно - профилируй
# EXPLAIN ANALYZE показывает реальный план запроса
# Потом переписывай в raw SQL если нужно
def rule_2_parametrize(self):
"""ВСЕГДА используй параметризованные запросы"""
# ❌ SQL injection!
query = f"SELECT * FROM users WHERE name = '{name}'"
# ✓ Безопасно
query = "SELECT * FROM users WHERE name = :name"
db.execute(query, {'name': name})
def rule_3_explain(self):
"""Проверяй план запроса"""
# EXPLAIN ANALYZE показывает реальное время выполнения
EXPLAIN ANALYZE SELECT ...
# Ищи Sequential Scans вместо Index Scans
def rule_4_test(self):
"""Тестируй сложные запросы"""
# Юнит тесты для raw SQL
def test_user_report_query(self):
result = db.execute(REPORT_QUERY).fetchall()
self.assertEqual(len(result), 2)
self.assertEqual(result[0]['user_count'], 100)
def rule_5_comment(self):
"""Комментируй сложные запросы"""
# Что делает этот запрос?
# Почему именно этот синтаксис?
query = """
-- Get unique users with published posts
-- Uses DISTINCT ON for efficiency over GROUP BY
SELECT DISTINCT ON (u.id) u.* FROM users u
INNER JOIN posts p ON u.id = p.user_id
WHERE p.status = 'published'
"""
print(RawSQLDecisions.__doc__)
Сравнение ORM vs Raw SQL
| Аспект | ORM | Raw SQL |
|---|---|---|
| Простота | ✓✓ | - |
| Безопасность | ✓✓ | Зависит |
| Производительность | ✓ | ✓✓ |
| Гибкость | ✓ | ✓✓ |
| Кроссплатформность | ✓✓ | - |
| Для сложных запросов | - | ✓✓ |
| Для CRUD | ✓✓ | - |
Практический алгоритм
def should_use_raw_sql(task):
# Шаг 1: Попробуй ORM
try:
result = ORM_approach()
if is_fast_enough(result):
return False # Используй ORM
except:
pass
# Шаг 2: Профилируй
import time
start = time.time()
orm_result = orm_approach()
orm_time = time.time() - start
if orm_time > THRESHOLD: # > 100ms
# Шаг 3: Оптимизируй с Raw SQL
start = time.time()
sql_result = raw_sql_approach()
sql_time = time.time() - start
if sql_time < orm_time * 0.5: # At least 2x faster
return True # Используй raw SQL
return False # Используй ORM
Вывод
Raw SQL нужен когда:
- ORM создает N+1 problem или неправильный JOIN
- Нужна максимальная производительность
- Используются специфичные для СУБД функции
- Сложные аналитические запросы
- Массовые операции (import, bulk updates)
Начинай с ORM и переходи на Raw SQL только если измеренные результаты показывают необходимость.