2008-10-07 13 views
142

Chcę zwrócić 10 rekordów z każdej sekcji w jednym zapytaniu. Czy ktoś może pomóc, jak to zrobić? Sekcja jest jedną z kolumn w tabeli.Wybierz 10 najlepszych rekordów dla każdej kategorii

Baza danych to SQL Server 2005. Chcę zwrócić 10 najlepszych według wprowadzonej daty. Sekcje są biznesowe, lokalne i funkcje. Dla jednej konkretnej daty chcę tylko górne (10) wiersze biznesowe (najnowszy wpis), górne (10) lokalne wiersze i górne (10) funkcje.

+0

Czy któraś z tych odpowiedzi zadziałała prawidłowo? –

Odpowiedz

4

Czy operator UNION może dla ciebie pracować? Zrób jeden SELECT dla każdej sekcji, a następnie połącz je ze sobą. Sądzę, że działałoby to tylko dla określonej liczby sekcji.

28

To działa na SQL Server 2005 (edytowane w celu odzwierciedlenia wyjaśnień):

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

To nie działa dla wierszy, w których sekcja ma wartość zerową. Musisz powiedzieć "gdzie (tt.Sekcja ma wartość zerową i t.Sekcja jest zerowa) lub tt.Sekcja = t.Sekcja" –

9

Jeśli wiesz, co odcinki są, można to zrobić:

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

Byłby to najłatwiejszy sposób robienia tego. –

+1

Ale byłoby to nieefektywne, jeśli masz 150 lub jeśli kategorie są zmienne według dnia, tygodnia, itd. –

+0

Oczywiście, ale by zacytować OP: "Sekcje to biznes, lokalność i funkcja". Jeśli masz trzy statyczne kategorie, jest to najlepszy sposób na zrobienie tego. – Blorgbeard

166

Jeśli używasz SQL 2005 możesz zrobić coś takiego ...

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 

Jeśli Twoje RankCriteria ma więzi, możesz zwrócić więcej niż 1 0 wierszy i rozwiązanie Matta może być lepsze dla ciebie.

+25

Jeśli naprawdę chcesz tylko top 10, zmień go na RowNumber() zamiast Rank(). Bez żadnych więzi. –

+3

Działa to, ale należy pamiętać, że funkcja rank() może zostać przekształcona w pełne sortowanie tabel przez program do planowania zapytań, jeśli nie ma indeksu, którego * pierwszym * kluczem jest RankCriteria. W takim przypadku możesz uzyskać lepszy przebieg, wybierając różne sekcje i stosując krzyż, aby wybrać 10 najlepszych zamówionych przez RankCriteria desc. –

+0

Świetna odpowiedź! Mam prawie dokładnie to, czego potrzebowałem. Skończyłem z 'DENSE_RANK', który nie ma żadnych luk w numeracji. +1 –

15

zrobić to w ten sposób:

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; 

zmiana: Ten przykład GROUP BY prac w MySQL i SQLite tylko, dlatego, że te bazy danych są bardziej liberalne niż standardowego SQL dotyczących GROUP BY. Większość implementacji SQL wymaga, aby wszystkie kolumny na liście wyboru, które nie są częścią wyrażenia zagregowanego, były również w grupie GROUP BY.

+1

Czy to działa? Jestem prawie pewien, że "a.somecolumn jest niepoprawna na liście select, ponieważ nie jest zawarta w funkcji agregującej lub klauzuli group by" dla każdej kolumny w artykułach z wyjątkiem article_id .. – Blorgbeard

+1

Powinieneś być w stanie dołączyć inne kolumny, które są funkcjonalnie zależne od kolumn podanych w GROUP BY. Kolumny, które nie są funkcjonalnie zależne, są niejednoznaczne. Ale masz rację, w zależności od implementacji RDBMS. Działa w MySQL, ale IIRC nie działa w InterBase/Firebird. –

+1

Czy to zadziałałoby w przypadku, gdyby jedenaście rekordów dla całej sekcji miało tę samą datę? Wszyscy mieliby liczbę 11, a wynik byłby pustym zbiorem. – Arth

8

Znam ten wątek jest trochę stary, ale ja po prostu wpadł na podobny problem (wybierz najnowszy artykuł z każdej kategorii) i jest to rozwiązanie wymyśliłem:

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 

This jest bardzo podobny do rozwiązania Darrela, ale pokonuje problem RANK, który może zwrócić więcej wierszy, niż zamierzano.

+0

Dlaczego warto korzystać z CTE Sir? Czy zmniejsza to zużycie pamięci? – toha

4

Q) Znajdowanie górę X rekordów z każdej grupy (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 rzędów wybranych.


+0

Pytanie dotyczyło SQL Server, a nie Oracle. – Craig

18
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 
+0

Co to jest tabela z aliasem "m"? – Chalky

+0

@ Chalky to literówka, powinno być 'r'. naprawiony. – lorond

+0

Pracował jak urok. Dziękuję Ci! –

57

W T-SQL, chciałbym zrobić:

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 
+1

: Proszę opisać lepiej swoje rozwiązanie. Zobacz: [How to Answer] (http://stackoverflow.com/questions/how-to-answer) – askmish

+0

Czy zapytanie wybrane na CTE może zawierać klauzul where? – toha

+1

@toha Tak, może – KindaTechy

3

Jeśli chcesz produkować wyjście pogrupowany według sekcji, wyświetlając tylko z najlepszymi n rekordy od każdego coś przekroju jak ten :

SECTION  SUBSECTION 

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

...następnie następujące powinny działać dość ogólnie ze wszystkimi bazami danych SQL. Jeśli chcesz 10 najlepszych, po prostu zmień 2 do 10 pod koniec zapytania.

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; 

Aby skonfigurować:

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'; 
+0

To nie działa, gdy chcę tylko pierwszy rekord dla każdej sekcji. Eliminuje wszystkie grupy sekcji, które mają więcej niż 1 rekord. Próbowałem, zastępując <= 2 <= 1 – nils

+0

@nils Są tylko trzy wartości przekroju: jeleń, pies i koń. Jeśli zmienisz zapytanie na <= 1, otrzymasz jedną podsekcję dla każdej sekcji: American Elk/Wapiti dla jelenia, Cocker Spaniel dla psa i Appaloosa dla konia. Są to również pierwsze wartości w każdej sekcji alfabetycznie. Kwerenda ma * na celu * wyeliminowanie wszystkich innych wartości. – Craig

+0

Ale kiedy próbuję uruchomić zapytanie, eliminuje wszystko, ponieważ liczba jest> = 1 dla wszystkiego. Nie zachowuje 1. podsekcji dla każdej sekcji. Czy możesz spróbować uruchomić zapytanie dla <= 1 i dać mi znać, jeśli otrzymasz pierwszy podsekcję dla każdej sekcji? – nils

5

Jeśli używamy SQL Server> = 2005, to możemy rozwiązać zadanie z jednej wybrać tylko:

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; 
+0

+1 Podoba mi się to rozwiązanie ze względu na jego prostotę, ale czy mógłbyś wyjaśnić, w jaki sposób użycie 'top 1' działa z instrukcją' case' w klauzuli 'order by' zwracającej 0 lub 1? – Ceres

+2

TOP 1 współpracuje z WITH TIES tutaj. Z KOŃCAMI oznacza, że ​​przy ORDER BY = 0, SELECT bierze ten rekord (z powodu TOP 1) i wszystkich innych, którzy mają ORDER BY = 0 (z powodu ZALEŻNOŚCI) –

0

Można spróbować to podejście. To zapytanie zwraca 10 najbardziej zaludnionych miast dla każdego kraju.

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; 
2

Wypróbowałem i zadziałało też z krawatami.

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 
Powiązane problemy