Дана схема базы данных с таблицами Users(id, name), Orders(id,user_id,amount), Payments(id,order_id,amount,status). Напишите и объясните SQL-запросы для: а) получения пользователей с просроченными платежами; б) агрегирования средней суммы заказов по пользователю; обсудите индексацию и проблемы масштабирования

25 Ноя в 15:54
1 +1
0
Ответы
1
а) Пользователи с просроченными платежами
SQL (вариант с JOIN):
SELECT DISTINCT u.*
FROM Users u
JOIN Orders o ON o.user_id = u.id
JOIN Payments p ON p.order_id = o.id
WHERE p.status = 'overdue';
Альтернатива с EXISTS (часто эффективнее, избегает дубликатов):
SELECT u.*
FROM Users u
WHERE EXISTS (
SELECT 1
FROM Orders o
JOIN Payments p ON p.order_id = o.id
WHERE o.user_id = u.id
AND p.status = 'overdue'
);
Пояснение:
- JOIN-версия возвращает пользователей, у которых есть связанный платеж со статусом 'overdue'; DISTINCT убирает дубли.
- EXISTS проверяет наличие хотя бы одной строки и обычно быстрее при большом числе платежей.
- Если у вас в Payments есть поле с датой срока (например due_date), лучше фильтровать по условию типа `p.due_date < now() AND p.status 'paid'`.
б) Средняя сумма заказов по пользователю
SQL (включая пользователей без заказов):
SELECT u.id, u.name, AVG(o.amount) AS avg_order_amount, COUNT(o.id) AS orders_count
FROM Users u
LEFT JOIN Orders o ON o.user_id = u.id
GROUP BY u.id, u.name;
Если нужны только пользователи с заказами — используйте INNER JOIN или добавьте HAVING:
... GROUP BY u.id, u.name
HAVING COUNT(o.id) > 0;
Пояснение:
- Используется агрегат AVG(o.amount)\mathrm{AVG}(o.amount)AVG(o.amount) и COUNT(o.id)\mathrm{COUNT}(o.id)COUNT(o.id).
- LEFT JOIN даёт значения NULL для AVG\mathrm{AVG}AVG у пользователей без заказов; INNER JOIN исключит таких пользователей.
Индексация и проблемы масштабирования (кратко)
Рекомендованные индексы:
- индекс для связи Orders -> Users: Orders(user_id)\text{Orders(user\_id)}Orders(user_id) (если нет FK-индекса).
- индекс для связи Payments -> Orders: Payments(order_id)\text{Payments(order\_id)}Payments(order_id) (обычно FK уже индексирован).
- для фильтрации по статусу (запрос а): Payments(status)\text{Payments(status)}Payments(status) или более предпочтительно составной/частичный индекс Payments(status, order_id)\text{Payments(status, order\_id)}Payments(status, order_id) или частичный индекс на часто запрашиваемом статусе (например только для 'overdue').
- для агрегирования можно добавить индекс по Orders(user_id)\text{Orders(user\_id)}Orders(user_id); но агрегаты типа AVG\mathrm{AVG}AVG обычно обходят весь набор строк и не полностью выигрывают от одиночного индекса.
Проблемы и решения при росте данных:
- Сканирование больших таблиц для агрегатов — дорого. Решения: предварительно агрегированные таблицы/материализованные представления, инкрементальные счётчики (denormalized sum и count в Users), или OLAP-реплика.
- Горизонтальное масштабирование: шардинг по user_id (если пользователи независимы) или партиционирование (range по дате или hash по user_id).
- Горизонтальное чтение: read replicas для аналитических запросов.
- Индексы занимают место и замедляют запись — балансируйте (например, частичные/композитные индексы).
- Мониторинг запросов (EXPLAIN/EXPLAIN ANALYZE) и планов — обязательно профилировать тяжелые запросы.
- Кэширование часто запрашиваемых агрегатов в Redis или CDN для уменьшения нагрузки.
Короткие практические советы:
- Для запроса (а) используйте EXISTS + индекс по Payments(status, order_id) или частичный индекс на 'overdue'.
- Для (б) при большом объёме держите агрегаты в materialized view или обновляйте денормализованные поля (sum, count) при вставках/обновлениях.
25 Ноя в 16:47
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир