- •Оглавление Извлечение данных. Оператор select
- •Оператор select
- •Предикаты (часть I)
- •Предикаты сравнения
- •Предикат between
- •Предикат in
- •Переименование столбцов и вычисления в результирующем наборе
- •Предикаты (часть 2) Предикат like
- •Использование значения null в условиях поиска Предикат is [not] null
- •Получение итоговых значений
- •Предложение group by
- •Предложение having
- •Использование в запросе нескольких источников записей
- •Явные операции соединения
- •Традиционные операции над множествами и оператор select
- •Декартово произведение
- •Объединение
- •Пересечение и разность
- •Предикат exists
- •Использование ключевых слов some | any и all с предикатами сравнения
- •Еще о подзапросах
- •Преобразование типов
- •Оператор case
- •Функции Transact-sql для обработки даты/времени
- •Функция dateadd
- •Функция datediff
- •Функция datepart
- •Функция datename
- •Функции работы со строками в ms sql server 2005
- •Функция substring
- •Функция reverse
- •Функция replace
- •Функции ltrim и rtrim
- •Функции lower и upper
- •Функция unicode
- •Функция nchar
- •Операторы модификации данных
- •Оператор insert
- •Вставка строк в таблицу, содержащую автоинкрементируемое поле
- •Оператор update
- •Оператор delete
- •Приложение 1. Описание учебных баз данных
- •1. Компьютерная фирма
- •2. Фирма вторсырья
- •3. Корабли
- •4. Аэрофлот
- •5. Окраска
- •Приложение 2. Список упражнений (select)
- •Как объединить данные из двух столбцов в один без использования union и join?
- •1. Union all
- •2. Full join
- •3. Unpivot
- •Комментарии
- •Как добавить новый столбец в таблицу между существующими столбцами?
- •Как вывести по n строк из каждой группы?
- •"Классическое" решение
- •1. Соединение
- •2. Подзапрос в предложении select
- •Решение на основе ранжирующих функций
- •Как удалить дубликаты строк из таблицы?
- •1. Нумерация строк
- •2. Ранжирование строк внутри групп дубликатов
- •3. Удаление дубликатов из виртуальной таблицы
- •Как удалить дубликаты строк при наличии первичного ключа?
- •Id name
- •Id_pk color
- •Id name color
- •Обновление таблицы t_details
- •Id_pk name color dup min_id
- •Id_pk color
- •Id name
- •Id_pk color
Как вывести по n строк из каждой группы?
Моисеенко С.И. (23-05-2009)
Такой вопрос возникает, например, когда на сайте требуется вывести по 3 самых свежих анонса в каждой новостной группе, или рекламу 5 самых популярных товаров в каждой категории.
Чтобы решить эту задачу, нужно выполнить разбиение всего набора строк на группы, произвести сортировку по требуемому критерию (по дате или количеству продаж) в пределах каждой группы и последовательно выбрать требуемое количество строк, начиная с первой строки в каждой группе.
Подобную задачу можно решать процедурно, используя временные таблицы и/или курсоры. Здесь же я хочу предложить два решения в стиле тех задач, которые мы решаем на сайте, т.е. одним запросом SELECT. Первое решение - "классическое", которое должно работать на большинстве СУБД; второе решение использует новые конструкции, которые появились в стандарте SQL:1999 и поддерживаются еще не так широко.
Рассмотрим следующую задачу:
Вывести из таблицы Product по три модели с наименьшими номерами из каждой группы, характеризуемой типом продукции.
Т.е. требует получить 3 компьютера, 3 ноутбука и 3 принтера, номера которых меньше номеров остальных моделей в своей группе. Поскольку номер модели является уникальным в таблице Product, то тут не возникает проблем с дубликатами. Заметим, что проблема дубликатов не является принципиальной, однако потребует уточнения формулировки.
"Классическое" решение
Это решение опирается на алгоритм нумерации строк, возвращаемых запросом. Т.е. мы нумеруем строки, а потом выбираем те их них, которые имеют номера, меньшие заданного числа. Следуя упомянутому алгоритму, запрос, который нумерует упорядоченный по возрастанию номера модели весь набор строк в таблице, можно записать так:
SELECT Pr1.model, COUNT(*) num
FROM Product Pr1 JOIN Product Pr2
ON Pr1.model >= Pr2.model
GROUP BY Pr1.model
Только для решения нашей задачи нужно пронумеровать не весь набор, а каждую группу в отдельности. Этого легко добиться, если в условие соединения таблиц добавить условие совпадения типов продукции, а также добавить группировку по типу:
SELECT Pr1.model, Pr1.type, COUNT(*) num
FROM Product Pr1 JOIN Product Pr2
ON Pr1.type = Pr2.type AND Pr1.model >= Pr2.model
GROUP BY Pr1.type, Pr1.model
HAVING COUNT (*) <= 3
ORDER BY type, model
Предложение
HAVING COUNT (*) < = 3
в соответствии с условием задачи ограничивает тремя количество строк в каждой группе. Фактически мы уже решили задачу. Осталось лишь добавить производителя (maker), что также можно сделать разными способами. Например, еще раз соединить по номеру модели приведенный выше запрос с таблицей Product, или использовать коррелирующий подзапрос в предложении SELECT. В учебных целях приведу оба подхода.
1. Соединение
SELECT maker, X.model, X.type
FROM product JOIN (
SELECT Pr1.model, Pr1.type
FROM Product Pr1 JOIN Product Pr2
ON Pr1.type = Pr2.type AND Pr1.model >= Pr2.model
GROUP BY Pr1.type, Pr1.model
HAVING COUNT (*) <= 3
) X on X.model = product.model
ORDER BY type,model
Здесь мы исключили лишний столбец num, который использовался в демонстрационных целях, поскольку нам не требуется выводить номер строки.