Как правильно сделать сортировку по общей стоимости для топ 10 клиентов
Что проверяют
Понимаете ли вы, что агрегат и топ считает база, а не Go-код после выгрузки всех заказов. Следом спрашивают, что делать с одинаковыми суммами на десятом месте и как быть, если данные уже в памяти.
Как отвечать
Основной ответ это один запрос:
SELECT c.id, c.name, SUM(o.amount) AS total FROM orders o JOIN clients c ON c.id = o.client_id GROUP BY c.id, c.name ORDER BY total DESC, c.id LIMIT 10
Сортировка идёт по агрегату, а не по сумме отдельного заказа. Второй ключ c.id делает порядок детерминированным: без него при равных суммах состав десятки может меняться от запуска к запуску. Если бизнесу нужны все, кто делит десятое место, в PostgreSQL 13+ есть FETCH FIRST 10 ROWS WITH TIES, в любой базе подойдёт DENSE_RANK().
На стороне Go:
- запрос:
db.QueryContext(ctx, ...),defer rows.Close(), циклrows.Next()соScan, после цикла обязательно проверитьrows.Err(); - деньги: в базе
numericили копейки вbigint, в Goint64копеек или decimal-библиотека.float64для сумм даёт ошибки округления; - сортировка в памяти: с Go 1.21 вместо
sort.Sortс тремя методами пишутslices.SortFunc(clients, func(a, b Client) int { return cmp.Compare(b.Total, a.Total) }). Если нужна устойчивость при равных ключах, естьslices.SortStableFunc; - топ-K из большого набора: полная сортировка стоит O(n log n), а куча на 10 элементов из
container/heapдаёт O(n log k) и держит в памяти только десятку.
Хорошо упомянуть индекс на orders(client_id) и фильтр по периоду: «топ за всё время» на растущей таблице со временем становится тяжёлым, и его часто считают заранее.
Где спотыкаются
Выгружают все заказы в Go и суммируют в map. Трафик и память растут вместе с таблицей, а база сделала бы то же самое быстрее.
Хранят суммы во float64. При сложении тысяч заказов копейки начинают расходиться с бухгалтерией.
Забывают про равные суммы. Без второго ключа сортировки выдача нестабильна, тесты мигают, а sort.Slice вообще не гарантирует порядок равных элементов.