Pilih 10 catatan teratas untuk setiap kategori

208

Saya ingin mengembalikan 10 catatan teratas dari setiap bagian dalam satu permintaan. Adakah yang bisa membantu dengan cara melakukannya? Bagian adalah salah satu kolom dalam tabel.

Database adalah SQL Server 2005. Saya ingin mengembalikan 10 besar berdasarkan tanggal yang dimasukkan. Bagian adalah bisnis, lokal, dan fitur. Untuk satu tanggal tertentu saya hanya menginginkan baris bisnis teratas (10) (entri terbaru), baris lokal top (10), dan fitur top (10).

jbcedge
sumber
Apakah ada dari jawaban ini yang cocok untuk Anda?
Kyle Delaney
3
Saya kira kita tidak akan pernah tahu ...
Denny
Sudah 12 tahun dan kami tidak tahu apakah ada yang berhasil.
aroma

Jawaban:

222

Jika Anda menggunakan SQL 2005 Anda dapat melakukan sesuatu seperti ini ...

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

Jika RankCriteria Anda memiliki ikatan maka Anda dapat mengembalikan lebih dari 10 baris dan solusi Matt mungkin lebih baik untuk Anda.

Darrel Miller
sumber
31
Jika Anda benar-benar hanya menginginkan 10 besar, ubah ke RowNumber () dan bukan Rank (). Tidak ada ikatan.
Mike L
3
Ini berfungsi, tetapi perlu diketahui bahwa peringkat () kemungkinan akan berubah menjadi semacam tabel penuh oleh perencana permintaan jika tidak ada indeks yang kunci pertama adalah RankCriteria. Dalam hal ini Anda mungkin mendapatkan jarak tempuh yang lebih baik dengan memilih bagian yang berbeda dan melamar melamar untuk memilih 10 besar yang dipesan oleh RankCriteria desc.
Joe Kearney
Jawaban Hebat! Memiliki saya hampir persis apa yang saya butuhkan. Saya akhirnya pergi dengan DENSE_RANKyang tidak memiliki kesenjangan dalam penomoran. +1
Michael Stramel
1
@Facbed Itu hanya alias di atas meja.
Darrel Miller
15
Bagi siapa pun yang menggunakan Sql Server, fungsi RowNumber () yang disebutkan oleh Mike L adalah ROW_NUMBER ().
randomraccoon
99

Dalam T-SQL, saya akan melakukan:

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
Phil Rabbitt
sumber
2
: Harap lebih deskriptif tentang solusi Anda. Rujuk: Cara Menjawab
askmish
Apakah kueri pemilihan di CTE dapat berisi klausa where?
toha
1
@toha Ya itu bisa
KindaTechy
1
Meskipun Anda mengatakan "Dalam T-SQL" ini berfungsi untuk semua database yang mengimplementasikan ROW_NUMBERfungsi ini. Sebagai contoh, saya telah menggunakan solusi ini dalam SQLite.
Tony
Ini berfungsi untuk postgres sql juga. Saya hanya perlu menggunakan "order by [prioritise_field] desc"
Phun
35

Ini berfungsi pada SQL Server 2005 (diedit untuk mencerminkan klarifikasi Anda):

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
Matt Hamilton
sumber
2
Ini tidak berfungsi untuk baris di mana Bagian adalah nol. Anda harus mengatakan "di mana (tt.Section adalah nol dan t.Section adalah null) atau 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
lorond
sumber
Apa itu tabel dengan alias 'm'?
Chalky
@ Chalky itu salah ketik, seharusnya r. tetap.
lorond
Bekerja seperti pesona. Terima kasih!
Ron Nuni
18

Saya melakukannya dengan cara ini:

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;

pembaruan: Contoh ini dari GROUP BY hanya berfungsi di MySQL dan SQLite, karena database tersebut lebih permisif daripada SQL standar tentang GROUP BY. Sebagian besar implementasi SQL mengharuskan semua kolom dalam daftar pilih yang bukan bagian dari ekspresi agregat juga dalam GROUP BY.

Bill Karwin
sumber
1
Apakah itu bekerja? Saya cukup yakin Anda akan "a.somecolumn tidak valid dalam daftar pilih karena tidak terkandung dalam fungsi agregat atau grup dengan klausa" untuk setiap kolom dalam artikel kecuali article_id ..
Blorgbeard keluar
1
Anda harus dapat menyertakan kolom lain yang secara fungsional bergantung pada kolom yang disebutkan dalam GROUP BY. Kolom yang tidak tergantung secara fungsional bersifat ambigu. Tapi Anda benar, tergantung pada implementasi RDBMS. Ia bekerja di MySQL tetapi IIRC gagal di InterBase / Firebird.
Bill Karwin
1
Apakah ini akan berhasil jika sebelas teratas mencatat untuk bagian semua memiliki tanggal yang sama? Mereka semua akan memiliki hitungan 11 dan hasilnya akan menjadi set kosong.
Arth
Tidak, Anda harus memiliki cara untuk memutuskan hubungan jika mereka semua memiliki tanggal yang sama. Lihat stackoverflow.com/questions/121387/… untuk contoh.
Bill Karwin 5-15
1
@carlosgg, jika artikel memiliki hubungan banyak-ke-banyak dengan bagian, maka Anda harus memiliki tabel persimpangan untuk memetakan artikel ke bagian mereka. Maka permintaan Anda harus bergabung dengan tabel persimpangan untuk hubungan m2m, dan kelompokkan dengan article_id dan bagian. Itu seharusnya membantu Anda, tetapi saya tidak akan menuliskan seluruh solusi dalam komentar.
Bill Karwin
16

Jika kita menggunakan SQL Server> = 2005, maka kita dapat menyelesaikan tugas dengan satu pilih saja:

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;
Vadim Loboda
sumber
1
+1 Saya menyukai solusi ini karena kesederhanaannya tetapi dapatkah Anda menjelaskan bagaimana menggunakan top 1karya dengan casepernyataan dalam order byklausa yang mengembalikan 0 atau 1?
Ceres
3
TOP 1 bekerja dengan DENGAN TIES di sini. DENGAN TIES berarti bahwa ketika ORDER BY = 0, maka SELECT mengambil catatan ini (karena TOP 1) dan semua orang lain yang memiliki ORDER BY = 0 (karena DENGAN TIES)
Vadim Loboda
9

Jika Anda tahu bagian apa itu, Anda dapat melakukannya:

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
Blorgbeard keluar
sumber
3
Ini akan menjadi cara termudah untuk melakukannya.
Hector Sosa Jr
3
Tetapi ini tidak akan efisien jika Anda memiliki 150 atau jika kategorinya bervariasi menurut hari, minggu, dll.
Rafa Barragan
1
Tentu, tetapi mengutip OP: "Bagian adalah bisnis, lokal, dan fitur". Jika Anda memiliki tiga kategori statis, ini adalah cara terbaik untuk melakukannya.
Blorgbeard keluar
9

Saya tahu utas ini agak lama tetapi saya baru saja mengalami masalah serupa (pilih artikel terbaru dari setiap kategori) dan ini adalah solusi yang saya buat:

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

Ini sangat mirip dengan solusi Darrel tetapi mengatasi masalah RANK yang mungkin menghasilkan lebih banyak baris daripada yang dimaksudkan.

Diadistis
sumber
Mengapa menggunakan CTE Sir? Apakah ini mengurangi konsumsi memori?
toha
@toha karena CTE lebih sederhana dan lebih mudah dipahami
Reversed Engineer
Jawaban bagus !! Hal ini dapat dioptimalkan dengan menggunakan inner JOINbukan LEFT JOIN, karena tidak akan pernah ada catatan untuk TopCategoryArticlestanpa sesuai Articlecatatan.
Reversed Engineer
6

Mencoba yang berikut dan bekerja dengan ikatan juga.

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
Raghu S
sumber
5

Jika Anda ingin menghasilkan keluaran yang dikelompokkan berdasarkan bagian, hanya menampilkan catatan atas n dari setiap bagian seperti ini:

SECTION     SUBSECTION

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

... maka berikut ini harus bekerja cukup umum dengan semua database SQL. Jika Anda menginginkan 10 teratas, cukup ubah 2 menjadi 10 di akhir kueri.

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;

Untuk mengatur:

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';
Craig
sumber
Ini tidak berfungsi ketika saya hanya ingin catatan pertama untuk setiap bagian. Ini menghilangkan semua grup bagian yang memiliki lebih dari 1 catatan. Saya mencoba dengan mengganti <= 2 dengan <= 1
nils
@nils Hanya ada tiga nilai bagian: rusa, anjing dan kuda. Jika Anda mengubah kueri menjadi <= 1, Anda mendapatkan satu subbagian untuk setiap bagian: Rusa Amerika / Wapiti untuk rusa, Cocker Spaniel untuk anjing dan Appaloosa untuk kuda. Ini juga merupakan nilai pertama di setiap bagian menurut abjad. Kueri dimaksudkan untuk menghilangkan semua nilai lainnya.
Craig
Tetapi ketika saya mencoba menjalankan kueri Anda, itu menghilangkan segalanya karena hitungnya adalah> = 1 untuk semuanya. Itu tidak mempertahankan ayat 1 untuk setiap bagian. Bisakah Anda mencoba menjalankan kueri Anda untuk <= 1 dan beri tahu saya jika Anda mendapatkan subbagian pertama untuk setiap bagian?
nils
@ nils Hai, saya membuat ulang database pengujian kecil ini dari skrip dan menjalankan kueri menggunakan <= 1, dan mengembalikan nilai subbagian pertama dari setiap bagian. Server database apa yang Anda gunakan? Selalu ada kesempatan terkait dengan database pilihan Anda. Saya hanya menjalankan ini di MySQL karena itu berguna dan berperilaku seperti yang diharapkan. Saya cukup yakin ketika saya melakukannya pertama kali (saya ingin memastikan apa yang saya posting benar-benar berfungsi tanpa debuggin), saya cukup yakin saya melakukannya menggunakan Sybase SQL Anywhere atau MS SQL Server.
Craig
itu bekerja dengan baik untuk saya di mysql. Saya mengubah kueri sedikit tidak yakin mengapa dia menggunakan <= untuk bidang varchar di sub-bagian .. saya mengubahnya ke dan x2.subection = x1.subsection
Mahen Nakar
4

Mungkinkah operator UNION bekerja untuk Anda? Memiliki satu PILIH untuk setiap bagian, lalu UNION bersama-sama. Kira itu hanya akan bekerja untuk sejumlah bagian yang tetap.

merah muda
sumber
4

T) Menemukan catatan TOP X dari setiap grup (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 baris dipilih.


bharathreddy
sumber
Pertanyaannya adalah tentang SQL Server, bukan Oracle.
Craig
2

Sementara pertanyaannya adalah tentang SQL Server 2005, kebanyakan orang telah pindah dan jika mereka menemukan pertanyaan ini, apa yang bisa menjadi jawaban yang lebih disukai dalam situasi lain adalah salah satu menggunakan CROSS APPLYseperti yang diilustrasikan dalam posting blog ini .

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

Kueri ini melibatkan 2 tabel. Permintaan OP hanya melibatkan 1 tabel, dalam hal solusi berbasis fungsi jendela mungkin lebih efisien.

Lukas Eder
sumber
1

Anda dapat mencoba pendekatan ini. Kueri ini mengembalikan 10 kota terpadat untuk setiap negara.

   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;
Ali
sumber
Solusi ini tidak lulus uji kasus ketika kami memiliki tabel dengan catatan satu negara dengan 9 populasi yang sama misalnya mengembalikan nol alih-alih mengembalikan semua 9 catatan yang tersedia secara berurutan. Adakah saran untuk memperbaiki masalah ini?
Mojgan Mazouchi