Выберите 10 лучших записей для каждой категории


208

Я хочу вернуть 10 лучших записей из каждого раздела в одном запросе. Может кто-нибудь помочь с тем, как это сделать? Раздел является одним из столбцов в таблице.

База данных - SQL Server 2005. Я хочу вернуть первые 10 по введенной дате. Разделы бизнес, локальные и тематические. Для одной конкретной даты мне нужны только верхние (10) бизнес-строк (самая последняя запись), верхние (10) локальных строк и верхние (10) объектов.


Работал ли какой-нибудь из этих ответов на вас?
Кайл Делани

3
Я думаю, мы никогда не узнаем ...
Денни

Прошло 12 лет, и мы не знаем, работал ли кто-нибудь из них.
аромат

Ответы:


222

Если вы используете SQL 2005, вы можете сделать что-то вроде этого ...

SELECT rs.Field1,rs.Field2 
    FROM (
        SELECT Field1,Field2, Rank() 
          over (Partition BY Section
                ORDER BY RankCriteria DESC ) AS Rank
        FROM table
        ) rs WHERE Rank <= 10

Если у вашего RankCriteria есть связи, вы можете вернуть более 10 строк, и решение Мэтта может быть лучше для вас.


31
Если вы действительно хотите получить топ-10, измените его на RowNumber () вместо Rank (). Никаких связей тогда.
Майк Л

3
Это работает, но имейте в виду, что rank (), вероятно, будет преобразован в сортировку полной таблицы планировщиком запросов, если нет индекса, первым ключом которого является RankCriteria. В этом случае вы можете получить больший пробег, выбрав отдельные секции и перекрестные заявки, чтобы выбрать 10 лучших, упорядоченных по RankCriteria desc.
Джо Керни

Отличный ответ! Получил мне почти точно то, что мне было нужно. Я закончил тем, что ушел DENSE_RANKбез пробелов в нумерации. +1
Майкл Штрамель

1
@Facbed Это просто псевдоним на столе.
Даррел Миллер

15
Для любого, кто использует Sql Server, функция RowNumber (), упомянутая Майком L, называется ROW_NUMBER ().
Случайный енот

99

В T-SQL я бы сделал:

WITH TOPTEN AS (
    SELECT *, ROW_NUMBER() 
    over (
        PARTITION BY [group_by_field] 
        order by [prioritise_field]
    ) AS RowNo 
    FROM [table_name]
)
SELECT * FROM TOPTEN WHERE RowNo <= 10

2
Пожалуйста, будьте более информативны в отношении вашего решения. См .: Как ответить
askmish

Может ли запрос select в CTE содержать предложение where?
Тоха

1
@toha Да, это возможно
KindaTechy

1
Хотя вы говорите «В T-SQL», это работает для любой базы данных, реализующей ROW_NUMBERфункцию. Например, я использовал это решение в SQLite.
Тони

Это работает и для Postgres sql. Мне просто нужно было использовать "order by [prioritise_field] desc"
Phun

35

Это работает на SQL Server 2005 (отредактировано, чтобы отразить ваше пояснение):

select *
from Things t
where t.ThingID in (
    select top 10 ThingID
    from Things tt
    where tt.Section = t.Section and tt.ThingDate = @Date
    order by tt.DateEntered desc
    )
    and t.ThingDate = @Date
order by Section, DateEntered desc

2
Это не работает для строк, где Section имеет значение null. Вам нужно будет сказать «где (tt.Section равно нулю, а t.Section равно нулю) или tt.Section = t.Section»
Мэтт Гамильтон,

29
SELECT r.*
FROM
(
    SELECT
        r.*,
        ROW_NUMBER() OVER(PARTITION BY r.[SectionID] ORDER BY r.[DateEntered] DESC) rn
    FROM [Records] r
) r
WHERE r.rn <= 10
ORDER BY r.[DateEntered] DESC

Что такое таблица с псевдонимом 'm'?
Мел

@ Мелкий, это опечатка, должно быть r. фиксированный.
Lorond

Работал как шарм. Спасибо!
Рон Нуни

18

Я делаю это так:

SELECT a.* FROM articles AS a
  LEFT JOIN articles AS a2 
    ON a.section = a2.section AND a.article_date <= a2.article_date
GROUP BY a.article_id
HAVING COUNT(*) <= 10;

обновление: этот пример GROUP BY работает только в MySQL и SQLite, потому что эти базы данных более универсальны, чем стандартный SQL в отношении GROUP BY. Большинство реализаций SQL требуют, чтобы все столбцы в списке выбора, которые не являются частью агрегатного выражения, также находились в GROUP BY.


1
Это работает? Я почти уверен, что «a.somecolumn недопустим в списке выбора, так как он не содержится в статистической функции или выражении group by» для каждого столбца в статьях, кроме article_id.
Blorgbeard отсутствует

1
Вы должны иметь возможность включать другие столбцы, которые функционально зависят от столбцов, названных в GROUP BY. Столбцы, которые не являются функционально зависимыми, неоднозначны. Но вы правы, в зависимости от реализации RDBMS. Это работает в MySQL, но IIRC терпит неудачу в InterBase / Firebird.
Билл Карвин

1
Будет ли это работать в том случае, если все верхние одиннадцать записей для раздела имеют одинаковую дату? Все они будут иметь число 11, и результатом будет пустой набор.
Арт

Нет, вам нужен какой-то способ разрыва связей, если у них у всех одна и та же дата. См. Для примера stackoverflow.com/questions/121387/…
Билл Карвин

1
@carlosgg, если статьи имеют отношение многие ко многим с разделами, то вам потребуется таблица пересечений, чтобы сопоставить статьи с их разделами. Тогда ваш запрос должен был бы присоединиться к таблице пересечений для отношения m2m и сгруппировать по article_id и section. Это должно помочь вам начать, но я не собираюсь выписывать все решение в комментарии.
Билл Карвин

16

Если мы используем SQL Server> = 2005, то мы можем решить задачу только одним выбором :

declare @t table (
    Id      int ,
    Section int,
    Moment  date
);

insert into @t values
(   1   ,   1   , '2014-01-01'),
(   2   ,   1   , '2014-01-02'),
(   3   ,   1   , '2014-01-03'),
(   4   ,   1   , '2014-01-04'),
(   5   ,   1   , '2014-01-05'),

(   6   ,   2   , '2014-02-06'),
(   7   ,   2   , '2014-02-07'),
(   8   ,   2   , '2014-02-08'),
(   9   ,   2   , '2014-02-09'),
(   10  ,   2   , '2014-02-10'),

(   11  ,   3   , '2014-03-11'),
(   12  ,   3   , '2014-03-12'),
(   13  ,   3   , '2014-03-13'),
(   14  ,   3   , '2014-03-14'),
(   15  ,   3   , '2014-03-15');


-- TWO earliest records in each Section

select top 1 with ties
    Id, Section, Moment 
from
    @t
order by 
    case 
        when row_number() over(partition by Section order by Moment) <= 2 
        then 0 
        else 1 
    end;


-- THREE earliest records in each Section

select top 1 with ties
    Id, Section, Moment 
from
    @t
order by 
    case 
        when row_number() over(partition by Section order by Moment) <= 3 
        then 0 
        else 1 
    end;


-- three LATEST records in each Section

select top 1 with ties
    Id, Section, Moment 
from
    @t
order by 
    case 
        when row_number() over(partition by Section order by Moment desc) <= 3 
        then 0 
        else 1 
    end;

1
+1 Мне нравится это решение за его простоту, но не могли бы вы объяснить, как использование top 1работает с caseоператором в order byпредложении, возвращающем 0 или 1?
Ceres

3
ТОП 1 работает с галстуками здесь. WITH TIES означает, что когда ORDER BY = 0, тогда SELECT берет эту запись (из-за TOP 1) и всех остальных, у которых ORDER BY = 0 (из-за WITH TIES)
Вадим Лобода

9

Если вы знаете, что такое разделы, вы можете сделать:

select top 10 * from table where section=1
union
select top 10 * from table where section=2
union
select top 10 * from table where section=3

3
Это был бы самый простой способ сделать это.
Гектор Соса-младший

3
Но это было бы неэффективно, если у вас есть 150 или если категории
Рафа Барраган,

1
Конечно, но процитировать ОП: «Разделы бизнес, локальные и функции». Если у вас есть три статические категории, это лучший способ сделать это.
Blorgbeard выходит

9

Я знаю, что этот поток немного староват, но я только что столкнулся с подобной проблемой (выберите самую новую статью из каждой категории), и вот решение, которое я придумал:

WITH [TopCategoryArticles] AS (
    SELECT 
        [ArticleID],
        ROW_NUMBER() OVER (
            PARTITION BY [ArticleCategoryID]
            ORDER BY [ArticleDate] DESC
        ) AS [Order]
    FROM [dbo].[Articles]
)
SELECT [Articles].* 
FROM 
    [TopCategoryArticles] LEFT JOIN 
    [dbo].[Articles] ON
        [TopCategoryArticles].[ArticleID] = [Articles].[ArticleID]
WHERE [TopCategoryArticles].[Order] = 1

Это очень похоже на решение Darrel, но преодолевает проблему RANK, которая может вернуть больше строк, чем предполагалось.


Зачем использовать CTE сэр? Это уменьшает потребление памяти?
Тоха

@toha, потому что CTE проще и легче понять
Обратный инженер

Отличный ответ !! Его можно оптимизировать, используя JOINвместо вместо inner LEFT JOIN, так как никогда не будет записи TopCategoryArticlesбез соответствующей Articleзаписи.
Обратно инженер

6

Перепробовал следующее и с галстуками тоже работал.

SELECT rs.Field1,rs.Field2 
FROM (
    SELECT Field1,Field2, ROW_NUMBER() 
      OVER (Partition BY Section
            ORDER BY RankCriteria DESC ) AS Rank
    FROM table
    ) rs WHERE Rank <= 10

5

Если вы хотите произвести вывод, сгруппированный по разделам, отображая только верхние n записей из каждого раздела примерно так:

SECTION     SUBSECTION

deer        American Elk/Wapiti
deer        Chinese Water Deer
dog         Cocker Spaniel
dog         German Shephard
horse       Appaloosa
horse       Morgan

... тогда следующее должно работать довольно широко со всеми базами данных SQL. Если вы хотите 10 лучших, просто измените 2 на 10 в конце запроса.

select
    x1.section
    , x1.subsection
from example x1
where
    (
    select count(*)
    from example x2
    where x2.section = x1.section
    and x2.subsection <= x1.subsection
    ) <= 2
order by section, subsection;

Установить:

create table example ( id int, section varchar(25), subsection varchar(25) );

insert into example select 0, 'dog', 'Labrador Retriever';
insert into example select 1, 'deer', 'Whitetail';
insert into example select 2, 'horse', 'Morgan';
insert into example select 3, 'horse', 'Tarpan';
insert into example select 4, 'deer', 'Row';
insert into example select 5, 'horse', 'Appaloosa';
insert into example select 6, 'dog', 'German Shephard';
insert into example select 7, 'horse', 'Thoroughbred';
insert into example select 8, 'dog', 'Mutt';
insert into example select 9, 'horse', 'Welara Pony';
insert into example select 10, 'dog', 'Cocker Spaniel';
insert into example select 11, 'deer', 'American Elk/Wapiti';
insert into example select 12, 'horse', 'Shetland Pony';
insert into example select 13, 'deer', 'Chinese Water Deer';
insert into example select 14, 'deer', 'Fallow';

Это не работает, когда я хочу только первую запись для каждого раздела. Это исключает все группы разделов, которые имеют более 1 записи. Я попытался заменить <= 2 на <= 1
nils

@nils Есть только три значения раздела: олень, собака и лошадь. Если вы измените запрос на <= 1, вы получите один подраздел для каждого раздела: американский лось / вапити для оленей, кокер-спаниель для собак и аппалуза для лошадей. Это также первые значения в каждом разделе в алфавитном порядке. Запрос предназначен для устранения всех других значений.
Крейг

Но когда я пытаюсь выполнить ваш запрос, он устраняет все, потому что счетчик> = 1 для всего. Это не сохраняет 1-й подраздел для каждого раздела. Можете ли вы попробовать выполнить свой запрос для <= 1 и сообщить мне, если вы получите первый подраздел для каждого раздела?
ноль

@nils Привет, я заново создал эту небольшую тестовую базу данных из сценариев и выполнил запрос, используя <= 1, и он возвратил первое значение подраздела из каждого раздела. Какой сервер базы данных вы используете? Всегда есть вероятность, что это связано с вашей базой данных выбора. Я просто запустил это в MySQL, потому что это было удобно, и он вел себя как ожидалось. Я совершенно уверен, что когда я делал это в первый раз (я хотел убедиться, что то, что я написал, действительно работало без отладки), я почти уверен, что сделал это с помощью Sybase SQL Anywhere или MS SQL Server.
Крейг,

это отлично сработало для меня в MySQL. Я изменил запрос, немного не зная, почему он использовал <= для поля varchar в подразделе. Я изменил его на x2.subsection = x1.subsection
Махен Накар

4

Может ли оператор UNION работать на вас? Сделайте один SELECT для каждого раздела, затем объедините их вместе. Думаю, это будет работать только для фиксированного числа разделов.


4

Q) Поиск TOP X записей из каждой группы (Oracle)

SQL> select * from emp e 
  2  where e.empno in (select d.empno from emp d 
  3  where d.deptno=e.deptno and rownum<3)
  4  order by deptno
  5  ;

 EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM     DEPTNO

  7782 CLARK      MANAGER         7839 09-JUN-81       2450                    10
  7839 KING       PRESIDENT            17-NOV-81       5000                    10
  7369 SMITH      CLERK           7902 17-DEC-80        800                    20
  7566 JONES      MANAGER         7839 02-APR-81       2975                    20
  7499 ALLEN      SALESMAN        7698 20-FEB-81       1600        300         30
  7521 WARD       SALESMAN        7698 22-FEB-81       1250        500         30

6 строк выбрано.



Вопрос был о SQL Server, а не о Oracle.
Крейг

2

Хотя вопрос был о SQL Server 2005, большинство людей пошли дальше, и если они найдут этот вопрос, то в других ситуациях предпочтительным ответом может стать тот, который будет использован, CROSS APPLYкак показано в этом сообщении в блоге .

SELECT *
FROM t
CROSS APPLY (
  SELECT TOP 10 u.*
  FROM u
  WHERE u.t_id = t.t_id
  ORDER BY u.something DESC
) u

Этот запрос включает в себя 2 таблицы. Запрос OP включает только 1 таблицу, в случае которой решение на основе оконной функции может быть более эффективным.


1

Вы можете попробовать этот подход. Этот запрос возвращает 10 самых населенных городов для каждой страны.

   SELECT city, country, population
   FROM
   (SELECT city, country, population, 
   @country_rank := IF(@current_country = country, @country_rank + 1, 1) AS country_rank,
   @current_country := country 
   FROM cities
   ORDER BY country, population DESC
   ) ranked
   WHERE country_rank <= 10;

Это решение не проходит тестовый случай, когда у нас есть таблица с записью одной страны с 9 одинаковым населением, например, он возвращает ноль вместо того, чтобы возвращать все 9 доступных записей по порядку. Любое предложение, чтобы исправить эту проблему?
Мойган Мазучи
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.