Виберіть топ-10 записів для кожної категорії


207

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

База даних - SQL Server 2005. Я хочу повернути топ-10 за вказаною датою. Розділи ділові, локальні та особливі. Для однієї конкретної дати я хочу лише верхні (10) бізнес-рядки (останній запис), верхній (10) місцеві рядки та верхній (10).


Чи спрацювала для вас будь-яка з цих відповідей?
Кайл Делані

3
Напевно, ми ніколи не дізнаємось ...
Денні

Минуло 12 років, і ми не знаємо, чи працював хтось із них.
аромат

Відповіді:


221

Якщо ви використовуєте 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
Це працює, але майте на увазі, що ранжир (), ймовірно, буде перетворений на повне сортування таблиці за допомогою планувальника запитів, якщо немає індексу, першим ключем якого є RankCriteria. У цьому випадку ви можете отримати кращий пробіг, вибравши окремі ділянки та перехресно застосувавшись, щоб вибрати 10 кращих, упорядкованих ранком критеріїв опису.
Джо Керні

Чудова відповідь! Зробив мене майже саме те, що мені було потрібно. Я закінчила свою роботу, в DENSE_RANKякій немає жодних прогалин у нумерації. +1
Майкл Страмель

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

15
Для всіх, хто використовує сервер Sql, функція 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
: Будь ласка, опишіть ваше рішення.
Довідка

Чи запит вибору в CTE може містити пункт де?
тоха

1
@toha Так може
KindaTechy

1
Хоча ви кажете "В T-SQL", це працює для будь-якої бази даних, що реалізує ROW_NUMBERфункцію. Наприклад, я використовував це рішення в SQLite.
Тоні

Він також працює для postgres sql. Мені просто довелося скористатися "замовленням [prioritise_field] desc"
Пн

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
Це не працює для рядків, де розділ недійсний. Вам потрібно буде сказати "де (tt.секція є нульовою, а t.секція - нульовою) або tt.секція = t.секція"
Метт Гамільтон,

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»?
Крейдяний

@Chalky це має бути помилка 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 вимагають, щоб усі стовпці у списку вибору, які не є частиною сукупного виразу, також були у групі BY.


1
Це працює? Я впевнений, що ви "a.somecolumn недійсний у списку вибору, оскільки він не міститься у сукупній функції або групі за допомогою пункту" для кожного стовпця в статтях, крім
Article_id

1
Ви повинні мати можливість включати інші стовпці, які функціонально залежать від стовпців (ів), названих у групі BY. Стовпці, не функціонально залежні, неоднозначні. Але ви праві, залежно від впровадження RDBMS. Він працює в MySQL, але IIRC виходить з ладу в InterBase / Firebird.
Білл Карвін

1
Чи вдасться це зробити у тому випадку, якщо одинадцять записів у розділі мали одну і ту ж дату? Всі вони мали б рахунки 11, і результат був би порожнім набором.
Арт

Ні, потрібно мати певний спосіб розірвати зв’язки, якщо всі вони мають одну і ту ж дату. Для прикладу див. Stackoverflow.com/questions/121387/… .
Білл Карвін

1
@carlosgg, якщо статті мають множинні стосунки з розділами, то вам потрібно мати таблицю перетину, щоб зіставити статті до їх розділів. Тоді ваш запит повинен був би приєднатись до таблиці перетину для відносин m2m та згрупувати за Article_id та розділом. Це повинно розпочати, але я не збираюся виписувати все рішення в коментарі.
Білл Карвін

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?
Церера

3
ТОП-1 працює з ЗНОМИ. З 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

Це дуже схоже на рішення Даррела, але долає проблему RANK, яка може повернути більше рядків, ніж планувалося.


Навіщо використовувати CTE Sir? Це зменшує споживання пам’яті?
тога

@toha тому, що CTE простіші та простіші для розуміння
Engineer

Чудова відповідь !! Це можна оптимізувати, використовуючи внутрішній JOINзамість 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 Є лише три значення розділу: олені, собака та кінь. Якщо ви зміните запит на <= 1, ви отримаєте по одному підрозділу для кожного розділу: американський лось / вапіті для оленів, кокер-спанієль для собаки та аппалуза для коня. Це також перші значення у кожному розділі за алфавітом. Запит призначений для усунення всіх інших значень.
Крейг

Але коли я намагаюся запустити ваш запит, він усуває все, тому що кількість рахунків> = 1 для всього. Він не зберігає 1-й підрозділ для кожного розділу. Чи можете ви спробувати запустити свій запит для <= 1, і повідомте мені, якщо ви отримаєте перший підрозділ для кожного розділу?
nils

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

він прекрасно працював для мене в mysql. Я трохи змінив запит, не знаючи, чому він використовував <= для поля варчара в підрозділі .. я змінив його на і x2.subsection = x1.subsection
Mahen Nakar

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 таблиці. Запит ОП включає лише 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.