Как сделать выборку топ-N записей по значению определённого поля?

«Как сделать выборку топ-N записей по значению определённого поля?» — вопрос из категории Базы данных, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Задача "топ-N" решается комбинацией сортировки (ORDER BY) и ограничения количества строк (LIMIT). Вот как я это делаю в разных сценариях.

Базовый случай: простой топ по значению поля

-- Топ-5 самых дорогих товаров
SELECT id, name, price 
FROM products 
ORDER BY price DESC 
LIMIT 5;

-- Топ-3 последних зарегистрированных пользователя
SELECT id, name, created_at 
FROM users 
ORDER BY created_at DESC 
LIMIT 3;

Сложные сценарии:

1. Топ внутри групп (аналог ROW_NUMBER()) Допустим, нужен самый дорогой товар в каждой категории. В старых версиях MySQL можно использовать переменные или подзапросы, но современный способ — оконные функции.

-- Используем оконную функцию (MySQL 8.0+, PostgreSQL, SQL Server)
SELECT * FROM (
    SELECT 
        id, name, category_id, price,
        ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY price DESC) as rank_in_category
    FROM products
) ranked_products
WHERE rank_in_category = 1; -- Берём только первые места в каждой категории

2. Выборка всех записей, попадающих в топ по пороговому значению Например, найти все товары, чья цена входит в топ-10% самых дорогих.

-- Определяем пороговое значение (цену, выше которой находятся топ-10% товаров)
SET @threshold = (
    SELECT MIN(price) FROM (
        SELECT price FROM products ORDER BY price DESC LIMIT (
            SELECT CEIL(COUNT(*) * 0.1) FROM products -- 10% от общего количества
        )
    ) top_prices
);

-- Выбираем все товары, цена которых >= этому порогу
SELECT * FROM products WHERE price >= @threshold ORDER BY price DESC;

3. Топ с учётом одинаковых значений (с использованием DENSE_RANK) Если несколько товаров имеют одинаковую цену и должны занимать одно место в рейтинге.

SELECT 
    id, name, price,
    DENSE_RANK() OVER (ORDER BY price DESC) as price_rank
FROM products
ORDER BY price_rank
LIMIT 10;

В контексте PHP и фреймворков:

// Laravel Eloquent
$topProducts = Product::orderBy('price', 'desc')->take(5)->get();

// Doctrine (Symfony)
$topProducts = $entityManager->createQuery(
    'SELECT p FROM AppEntityProduct p ORDER BY p.price DESC'
)->setMaxResults(5)->getResult();

Ключевой момент: LIMIT без ORDER BY даёт недетерминированный результат, так как порядок записей без явной сортировки не гарантирован. Всегда используйте ORDER BY для задач, связанных с "топом".