Как написать SQL запрос для выбора всех категорий с последним ID новости в каждой категории
Что проверяют
Умеете ли вы писать группирующие запросы без типичных ловушек: потерянные категории без новостей и дубли при одинаковых датах. Следом часто просят решение, которое выдержит таблицу новостей на десятки миллионов строк, и спрашивают, как считать результат в Go.
Как отвечать
Сначала уточните, что значит «последняя»: максимальный id или самая свежая по created_at. Если по id, хватает группировки:
SELECT c.id, c.name, MAX(n.id) AS last_news_id FROM categories c LEFT JOIN news n ON n.category_id = c.id GROUP BY c.id, c.name;
LEFT JOIN оставляет пустые категории с NULL, обычный JOIN их молча выбросит. Если «последняя» по дате, варианты такие:
- оконная функция:
ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY created_at DESC, id DESC)и фильтрrn = 1. Второй ключ сортировки нужен, чтобы при равных датах результат был детерминированным; DISTINCT ONв PostgreSQL:SELECT DISTINCT ON (category_id) category_id, id FROM news ORDER BY category_id, created_at DESC, id DESC;LEFT JOIN LATERALс подзапросомORDER BY created_at DESC LIMIT 1для каждой категории. При малом числе категорий и индексе по(category_id, created_at DESC)это несколько коротких проходов по индексу вместо чтения всей таблицы.
Под любой вариант нужен составной индекс, начинающийся с category_id. Проверьте план через EXPLAIN ANALYZE.
В Go помните, что last_news_id может быть NULL: в database/sql сканируйте в sql.NullInt64 или указатель *int64, иначе Scan вернёт ошибку на пустой категории.
Где спотыкаются
Пишут WHERE n.created_at = (SELECT MAX(created_at) ...). При двух новостях с одинаковым временем категория попадает в выборку дважды.
Используют INNER JOIN и теряют категории без новостей, хотя задача просит все категории.
Добавляют n.title в SELECT рядом с MAX(n.id). PostgreSQL такой запрос отклонит, а MySQL без режима ONLY_FULL_GROUP_BY вернёт заголовок случайной строки группы.