Wählen Sie die Top 10 Datensätze für jede Kategorie aus


207

Ich möchte die Top 10 Datensätze aus jedem Abschnitt in einer Abfrage zurückgeben. Kann jemand helfen, wie es geht? Abschnitt ist eine der Spalten in der Tabelle.

Die Datenbank ist SQL Server 2005. Ich möchte die Top 10 nach eingegebenem Datum zurückgeben. Abschnitte sind geschäftlich, lokal und Feature. Für ein bestimmtes Datum möchte ich nur die obersten (10) Geschäftszeilen (letzter Eintrag), die obersten (10) lokalen Zeilen und die obersten (10) Funktionen.


Hat eine dieser Antworten für Sie funktioniert?
Kyle Delaney

3
Ich denke, wir werden es nie erfahren ...
Denny

Es ist 12 Jahre her und wir wissen nicht, ob einer von denen funktioniert hat.
Aroma

Antworten:


221

Wenn Sie SQL 2005 verwenden, können Sie so etwas tun ...

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

Wenn Ihre RankCriteria Verbindungen hat, können Sie mehr als 10 Zeilen zurückgeben, und Matts Lösung ist möglicherweise besser für Sie.


31
Wenn Sie wirklich nur die Top 10 wollen, ändern Sie sie in RowNumber () anstelle von Rank (). Dann keine Krawatten.
Mike L

3
Dies funktioniert, aber beachten Sie, dass rank () vom Abfrageplaner wahrscheinlich in eine vollständige Tabellensortierung umgewandelt wird, wenn es keinen Index gibt, dessen erster Schlüssel die RankCriteria ist. In diesem Fall erhalten Sie möglicherweise eine bessere Kilometerleistung, indem Sie die verschiedenen Abschnitte auswählen und sich kreuzen, um die Top 10 auszuwählen, die nach RankCriteria desc geordnet sind.
Joe Kearney

Gute Antwort! Habe mir fast genau das besorgt, was ich brauchte. Am Ende habe DENSE_RANKich keine Lücken in der Nummerierung. +1
Michael Stramel

1
@Facbed Es ist nur ein Alias ​​auf dem Tisch.
Darrel Miller

15
Für jeden, der SQL Server verwendet, lautet die von Mike L erwähnte Funktion RowNumber () ROW_NUMBER ().
Randomraccoon

99

In T-SQL würde ich Folgendes tun:

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
: Bitte beschreiben Sie Ihre Lösung genauer. Siehe: Wie zu
antworten

Kann eine ausgewählte Abfrage bei CTE eine where-Klausel enthalten?
Toha

1
@toha Ja, es kann
KindaTechy

1
Obwohl Sie "In T-SQL" sagen, funktioniert dies für jede Datenbank, die die ROW_NUMBERFunktion implementiert . Zum Beispiel habe ich diese Lösung in SQLite verwendet.
Tony

Es funktioniert auch für Postgres SQL. Ich musste nur "order by [priorise_field] desc" verwenden
Phun

35

Dies funktioniert unter SQL Server 2005 (bearbeitet, um Ihre Klarstellung widerzuspiegeln):

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
Dies funktioniert jedoch nicht für Zeilen, in denen Section null ist. Sie müssten sagen "wo (tt.Section ist null und t.Section ist null) oder tt.Section = t.Section"
Matt Hamilton

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

Was ist eine Tabelle mit dem Alias ​​'m'?
Chalky

@ Chalky es ist Tippfehler, sollte sein r. Fest.
Lorond

Lief wie am Schnürchen. Danke dir!
Ron Nuni

18

Ich mache es so:

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;

Update: Dieses Beispiel für GROUP BY funktioniert nur in MySQL und SQLite, da diese Datenbanken in Bezug auf GROUP BY zulässiger sind als Standard-SQL. Die meisten SQL-Implementierungen erfordern, dass sich alle Spalten in der Auswahlliste, die nicht Teil eines Aggregatausdrucks sind, auch in GROUP BY befinden.


1
Funktioniert es? Ich bin mir ziemlich sicher, dass Sie "a.somecolumn in der Auswahlliste ungültig sind, da es nicht in einer Aggregatfunktion oder der group by-Klausel enthalten ist" für jede Spalte in Artikeln außer article_id ..
Blorgbeard ist

1
Sie sollten in der Lage sein, andere Spalten einzuschließen, die funktional von den in GROUP BY genannten Spalten abhängen. Spalten, die nicht funktional abhängig sind, sind mehrdeutig. Aber Sie haben Recht, abhängig von der RDBMS-Implementierung. Es funktioniert in MySQL, aber IIRC schlägt in InterBase / Firebird fehl.
Bill Karwin

1
Würde dies funktionieren, wenn die elf besten Datensätze für einen Abschnitt alle dasselbe Datum hätten? Sie würden alle 11 zählen und das Ergebnis wäre eine leere Menge.
Arth

Nein, Sie müssen eine Möglichkeit haben, Krawatten zu lösen, wenn alle das gleiche Datum haben. Ein Beispiel finden Sie unter stackoverflow.com/questions/121387/… .
Bill Karwin

1
@carlosgg, wenn Artikel eine Viele-zu-Viele-Beziehung zu Abschnitten haben, benötigen Sie eine Schnittpunkttabelle, um Artikel ihren Abschnitten zuzuordnen. Dann müsste Ihre Abfrage mit einer Schnittpunkttabelle für die m2m-Beziehung verknüpft und nach article_id und section gruppiert werden. Das sollte Ihnen den Einstieg erleichtern, aber ich werde nicht die gesamte Lösung in einem Kommentar aufschreiben.
Bill Karwin

16

Wenn wir SQL Server> = 2005 verwenden, können wir die Aufgabe mit nur einer Auswahl lösen :

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 Ich mag diese Lösung wegen ihrer Einfachheit, aber können Sie erklären, wie die Verwendung top 1mit der caseAnweisung in der order byKlausel funktioniert, die 0 oder 1 zurückgibt?
Ceres

3
TOP 1 arbeitet hier mit WITH TIES. WITH TIES bedeutet, dass wenn ORDER BY = 0 ist, SELECT diesen Datensatz (wegen TOP 1) und alle anderen, die ORDER BY = 0 haben (wegen WITH TIES)
Vadim Loboda

9

Wenn Sie die Abschnitte kennen, können Sie Folgendes tun:

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
Dies wäre der einfachste Weg, dies zu tun.
Hector Sosa Jr

3
Dies wäre jedoch ineffizient, wenn Sie 150 haben oder wenn die Kategorien nach Tag, Woche usw. variieren.
Rafa Barragan

1
Sicher, aber um OP zu zitieren: "Abschnitte sind geschäftlich, lokal und Feature". Wenn Sie drei statische Kategorien haben, ist dies der beste Weg, dies zu tun.
Blorgbeard ist

9

Ich weiß, dass dieser Thread ein bisschen alt ist, aber ich bin gerade auf ein ähnliches Problem gestoßen (wählen Sie den neuesten Artikel aus jeder Kategorie aus), und dies ist die Lösung, die ich gefunden habe:

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

Dies ist der Lösung von Darrel sehr ähnlich, überwindet jedoch das RANK-Problem, das möglicherweise mehr Zeilen als beabsichtigt zurückgibt.


Warum CTE Sir verwenden? Reduziert es den Speicherverbrauch?
Toha

@toha, weil CTEs einfacher und leichter zu verstehen sind
Reversed Engineer

Gute Antwort!! Es könnte durch Verwendung von inner JOINanstelle von optimiert werden LEFT JOIN, da es TopCategoryArticlesohne einen entsprechenden ArticleDatensatz niemals einen Datensatz geben wird .
Umgekehrter Ingenieur

6

Versuchte das Folgende und es funktionierte auch mit Krawatten.

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

Wenn Sie eine nach Abschnitten gruppierte Ausgabe erstellen möchten, zeigen Sie nur die obersten n Datensätze aus jedem Abschnitt wie folgt an:

SECTION     SUBSECTION

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

... dann sollte das Folgende ziemlich allgemein mit allen SQL-Datenbanken funktionieren. Wenn Sie die Top 10 möchten, ändern Sie einfach die 2 gegen Ende der Abfrage in eine 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;

So richten Sie ein:

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

Dies funktioniert nicht, wenn ich nur den ersten Datensatz für jeden Abschnitt möchte. Es werden alle Abschnittsgruppen entfernt, die mehr als einen Datensatz haben. Ich habe versucht, indem ich <= 2 durch <= 1 ersetzt habe
nils

@nils Es gibt nur drei Abschnittswerte: Hirsch, Hund und Pferd. Wenn Sie die Abfrage auf <= 1 ändern, erhalten Sie einen Unterabschnitt für jeden Abschnitt: American Elk / Wapiti für Hirsche, Cocker Spaniel für Hund und Appaloosa für Pferd. Dies sind auch die ersten Werte in jedem Abschnitt in alphabetischer Reihenfolge. Die Abfrage wird gemeint , alle anderen Werte zu eliminieren.
Craig

Aber wenn ich versuche, Ihre Abfrage auszuführen, wird alles entfernt, da die Anzahl für alles> = 1 ist. Der 1. Unterabschnitt wird nicht für jeden Abschnitt beibehalten. Können Sie versuchen, Ihre Abfrage für <= 1 auszuführen und mich wissen zu lassen, ob Sie den ersten Unterabschnitt für jeden Abschnitt erhalten?
Null

@nils Hallo, ich habe diese kleine Testdatenbank aus den Skripten neu erstellt und die Abfrage mit <= 1 ausgeführt. Dabei wurde der erste Unterabschnittswert aus jedem Abschnitt zurückgegeben. Welchen Datenbankserver verwenden Sie? Es besteht immer die Möglichkeit, dass es sich um eine Datenbank Ihrer Wahl handelt. Ich habe dies gerade in MySQL ausgeführt, weil es praktisch war und sich wie erwartet verhielt. Ich bin mir ziemlich sicher, als ich es das erste Mal gemacht habe (ich wollte sicherstellen, dass das, was ich gepostet habe, tatsächlich ohne Debuggin funktioniert), ich bin mir ziemlich sicher, dass ich es entweder mit Sybase SQL Anywhere oder MS SQL Server gemacht habe.
Craig

es hat perfekt für mich in mysql funktioniert. Ich habe eine Abfrage ein wenig geändert, nicht sicher, warum er <= für das varchar-Feld im Unterabschnitt verwendet hat. Ich habe sie in und x2.subsection = x1.subsection geändert
Mahen Nakar

4

Könnte der UNION- Operator für Sie arbeiten? Haben Sie eine SELECT für jeden Abschnitt, dann UNION sie zusammen. Ich denke, es würde nur für eine feste Anzahl von Abschnitten funktionieren.


4

F) Finden von TOP X-Datensätzen aus jeder Gruppe (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 Zeilen ausgewählt.



Die Frage betraf SQL Server, nicht Oracle.
Craig

2

Während die Frage nach SQL Server 2005 war, haben die meisten Menschen bewegten auf und wenn sie diese Frage nicht finden, was die bevorzugte Antwort in anderen Situationen sein könnte , ist eine Verwendung CROSS APPLYals in diesem Blog - Eintrag dargestellt .

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

Diese Abfrage umfasst 2 Tabellen. Die Abfrage des OP umfasst nur eine Tabelle. In diesem Fall ist eine auf Fensterfunktionen basierende Lösung möglicherweise effizienter.


1

Sie können diesen Ansatz ausprobieren. Diese Abfrage gibt 10 bevölkerungsreichste Städte für jedes Land zurück.

   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;

Diese Lösung besteht keinen Testfall, wenn wir eine Tabelle mit einem Datensatz eines Landes mit 9 derselben Bevölkerung haben. Beispielsweise wird null zurückgegeben, anstatt alle 9 verfügbaren Datensätze der Reihe nach zurückzugeben. Irgendwelche Vorschläge zur Behebung dieses Problems?
Mojgan Mazouchi
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.