Виберіть топ-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.