← Назад к вопросам
Каким образом собрать сумму заказов в SQL?
1.0 Junior🔥 181 комментариев
#Базы данных (SQL)
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Сумма заказов в SQL
Это очень распространённый запрос при работе с e-commerce и транзакциями. Вот различные подходы и паттерны.
Базовый запрос
-- Сумма всех заказов
SELECT SUM(amount) as total_sum
FROM orders;
-- Результат: 154500.00
Сумма по категориям (GROUP BY)
-- Сумма заказов по статусу
SELECT
status,
SUM(amount) as total_sum,
COUNT(*) as order_count,
AVG(amount) as average_order
FROM orders
GROUP BY status
ORDER BY total_sum DESC;
-- Результат:
-- status | total_sum | order_count | average_order
-- completed | 150000.00 | 980 | 153.06
-- pending | 4500.00 | 50 | 90.00
-- cancelled | 0.00 | 20 | 0.00
Сумма по пользователям с JOIN
-- Сумма заказов каждого пользователя с его деталями
SELECT
u.id,
u.name,
u.email,
SUM(o.amount) as total_spent,
COUNT(o.id) as order_count,
MAX(o.created_at) as last_order_date
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name, u.email
ORDER BY total_spent DESC;
-- Результат:
-- id | name | email | total_spent | order_count | last_order_date
-- 1 | Alice | alice@example.com | 15000.00 | 45 | 2024-03-15
-- 2 | Bob | bob@example.com | 8500.00 | 23 | 2024-03-10
-- 3 | Charlie | charlie@e.com | 0.00 | 0 | NULL
Сумма с фильтрацией (WHERE vs HAVING)
-- ❌ Неправильно - WHERE выполняется до GROUP BY
SELECT
user_id,
SUM(amount) as total_sum
FROM orders
WHERE status = 'completed' -- Фильтр перед агрегацией
GROUP BY user_id;
-- ✅ Правильно - HAVING выполняется после GROUP BY
SELECT
user_id,
SUM(amount) as total_sum
FROM orders
GROUP BY user_id
HAVING SUM(amount) > 1000; -- Фильтр после агрегации
-- Когда использовать что:
-- WHERE: фильтруешь строки перед группировкой
-- HAVING: фильтруешь результаты группировки
Сумма с датами (DATE_TRUNC, DATE_FORMAT)
-- PostgreSQL: сумма по дням
SELECT
DATE_TRUNC('day', created_at) as order_date,
SUM(amount) as daily_sum
FROM orders
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY DATE_TRUNC('day', created_at)
ORDER BY order_date DESC;
-- MySQL: сумма по месяцам
SELECT
DATE_FORMAT(created_at, '%Y-%m') as month,
SUM(amount) as monthly_sum
FROM orders
GROUP BY DATE_FORMAT(created_at, '%Y-%m')
ORDER BY month DESC;
-- SQLite: сумма по неделям
SELECT
strftime('%Y-W%W', created_at) as week,
SUM(amount) as weekly_sum
FROM orders
GROUP BY strftime('%Y-W%W', created_at)
ORDER BY week DESC;
Сумма с условием (CASE WHEN)
-- Разбить сумму по типам платежа в одном запросе
SELECT
SUM(CASE WHEN payment_method = 'credit_card' THEN amount ELSE 0 END) as credit_card_sum,
SUM(CASE WHEN payment_method = 'paypal' THEN amount ELSE 0 END) as paypal_sum,
SUM(CASE WHEN payment_method = 'crypto' THEN amount ELSE 0 END) as crypto_sum,
SUM(amount) as total_sum
FROM orders
WHERE status = 'completed';
-- Результат:
-- credit_card_sum | paypal_sum | crypto_sum | total_sum
-- 85000.00 | 50000.00 | 15000.00 | 150000.00
Сумма с окном функций (Window Functions)
-- Накопленная сумма (running total)
SELECT
created_at,
amount,
SUM(amount) OVER (ORDER BY created_at) as running_total,
SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at) as user_running_total
FROM orders
ORDER BY created_at;
-- Результат:
-- created_at | amount | running_total | user_running_total
-- 2024-01-01 | 100 | 100 | 100
-- 2024-01-02 | 200 | 300 | 200
-- 2024-01-03 | 150 | 450 | 150
Сумма с ROLLUP (итоги и подитоги)
-- PostgreSQL и некоторые другие БД
SELECT
COALESCE(category, 'TOTAL') as category,
COALESCE(status, 'ALL_STATUS') as status,
SUM(amount) as total_sum
FROM orders
GROUP BY ROLLUP(category, status)
ORDER BY category, status;
-- Результат:
-- category | status | total_sum
-- Electronics | completed | 45000.00
-- Electronics | pending | 5000.00
-- Electronics | NULL | 50000.00 (итого для категории)
-- Clothes | completed | 35000.00
-- Clothes | NULL | 35000.00
-- NULL | NULL | 85000.00 (общий итог)
Сумма с CTE (Common Table Expression)
-- Сложный запрос с временной таблицей
WITH order_summary AS (
SELECT
user_id,
SUM(amount) as total_spent,
COUNT(*) as order_count
FROM orders
WHERE created_at >= CURRENT_DATE - INTERVAL '90 days'
GROUP BY user_id
),
top_customers AS (
SELECT
user_id,
total_spent,
order_count,
ROW_NUMBER() OVER (ORDER BY total_spent DESC) as rank
FROM order_summary
WHERE total_spent > 5000
)
SELECT
tc.rank,
u.name,
tc.total_spent,
tc.order_count
FROM top_customers tc
JOIN users u ON tc.user_id = u.id
ORDER BY tc.rank;
Сумма товаров в заказе (agrevate items)
-- Если заказ состоит из позиций (line items)
-- Table: orders (id, user_id, created_at)
-- Table: order_items (id, order_id, product_id, quantity, price)
SELECT
o.id as order_id,
u.name,
o.created_at,
SUM(oi.quantity * oi.price) as order_total,
COUNT(oi.id) as item_count,
SUM(oi.quantity) as total_quantity
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN order_items oi ON o.id = oi.order_id
GROUP BY o.id, u.name, o.created_at
ORDER BY o.created_at DESC;
-- Это корректное использование денормализации
-- order_total должна считаться из order_items, не храниться в orders
Оптимизация больших сумм
-- ❌ Медленно (полный scan таблицы):
SELECT SUM(amount) FROM orders;
-- ✅ Быстро (с индексом и фильтрацией):
SELECT SUM(amount) FROM orders
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
AND status = 'completed';
-- Нужен индекс:
CREATE INDEX idx_orders_created_status
ON orders(created_at, status);
-- Проверить план:
EXPLAIN SELECT SUM(amount) FROM orders
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days';
На Python с SQLAlchemy
from sqlalchemy import func, select
from sqlalchemy.orm import Session
# Базовая сумма
def get_total_sum(session: Session) -> float:
result = session.query(func.sum(Order.amount)).scalar()
return result or 0
# Сумма по статусу
def get_sum_by_status(session: Session) -> dict:
results = session.query(
Order.status,
func.sum(Order.amount).label('total_sum'),
func.count(Order.id).label('order_count')
).group_by(Order.status).all()
return {row.status: row.total_sum for row in results}
# С фильтрацией
def get_user_total_spent(session: Session, user_id: int) -> float:
result = session.query(func.sum(Order.amount)).filter(
Order.user_id == user_id,
Order.status == 'completed'
).scalar()
return result or 0
# Сумма с GROUP BY
from sqlalchemy import and_
def get_stats_by_date(session: Session):
from datetime import datetime, timedelta
thirty_days_ago = datetime.now() - timedelta(days=30)
results = session.query(
func.date(Order.created_at).label('order_date'),
func.sum(Order.amount).label('daily_sum'),
func.avg(Order.amount).label('avg_amount')
).filter(
Order.created_at >= thirty_days_ago
).group_by(
func.date(Order.created_at)
).order_by('order_date').all()
return results
Частые ошибки
-- ❌ Ошибка 1: Неагрегированные колонки в SELECT
SELECT
user_id,
name, -- ❌ Неагрегирована! Какого пользователя вернуть если много?
SUM(amount) as total
FROM orders
GROUP BY user_id;
-- ✅ Правильно:
SELECT
user_id,
u.name,
SUM(o.amount) as total
FROM orders o
JOIN users u ON o.user_id = u.id
GROUP BY o.user_id, u.name;
-- ❌ Ошибка 2: SUM(NULL)
SELECT SUM(amount) FROM orders;
-- Если amount может быть NULL, SUM пропустит NULL значения
-- Но если все значения NULL, результат будет NULL
-- ✅ Правильно:
SELECT COALESCE(SUM(amount), 0) as total
FROM orders;
-- ❌ Ошибка 3: Integer overflow
-- Если sum() получится больше чем max int, переполнение!
SELECT SUM(CAST(amount as BIGINT)) as total -- ✅ Правильно
FROM orders;
Производительность
Размер таблицы: 10 млн. заказов
Без индекса:
SELECT SUM(amount) FROM orders;
-- Time: 2500ms (полный scan!)
С индексом на amount:
CREATE INDEX idx_amount ON orders(amount);
SELECT SUM(amount) FROM orders;
-- Time: 2400ms (индекс не помогает, всё равно полный scan)
С фильтрацией и индексом:
CREATE INDEX idx_created_status ON orders(created_at, status);
SELECT SUM(amount) FROM orders
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
AND status = 'completed';
-- Time: 15ms (индекс очень помогает!)
Вывод: всегда используй WHERE для ограничения данных!
Вывод: SUM() — одна из самых полезных агрегатных функций в SQL. Используй GROUP BY для детализации, HAVING для фильтрации результатов, и Window Functions для более сложных аналитик задач.