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

Каким образом собрать сумму заказов в 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 для более сложных аналитик задач.

Каким образом собрать сумму заказов в SQL? | PrepBro