Chọn 10 bản ghi hàng đầu cho mỗi thể loại


207

Tôi muốn trả về 10 bản ghi hàng đầu từ mỗi phần trong một truy vấn. Bất cứ ai có thể giúp làm thế nào để làm điều đó? Phần là một trong các cột trong bảng.

Cơ sở dữ liệu là SQL Server 2005. Tôi muốn trả lại top 10 theo ngày đã nhập. Các phần là kinh doanh, địa phương và tính năng. Đối với một ngày cụ thể, tôi chỉ muốn các hàng kinh doanh (10) hàng đầu (mục nhập gần đây nhất), hàng đầu (10) hàng địa phương và các tính năng hàng đầu (10).


Có bất kỳ câu trả lời trong số này làm việc cho bạn?
Kyle Delaney

3
Tôi đoán chúng ta sẽ không bao giờ biết ...
Denny

Đã 12 năm và chúng tôi không biết có ai trong số họ làm việc không.
hương thơm

Câu trả lời:


221

Nếu bạn đang sử dụng SQL 2005, bạn có thể làm một cái gì đó như thế này ...

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

Nếu Thứ hạng của bạn có quan hệ thì bạn có thể trả lại hơn 10 hàng và giải pháp của Matt có thể tốt hơn cho bạn.


31
Nếu bạn thực sự chỉ muốn top 10, hãy đổi nó thành RowNumber () thay vì Rank (). Không có quan hệ sau đó.
Mike L

3
Điều này hoạt động, nhưng lưu ý rằng xếp hạng () có khả năng được trình lập kế hoạch truy vấn sắp xếp thành bảng đầy đủ nếu không có chỉ mục mà khóa đầu tiên là RankCriteria. Trong trường hợp này, bạn có thể nhận được số dặm tốt hơn khi chọn các phần riêng biệt và áp dụng chéo để chọn ra top 10 theo thứ tự của RankCriteria desc.
Joe Kearney

Câu trả lời chính xác! Có tôi gần như chính xác những gì tôi cần. Tôi đã kết thúc với DENSE_RANKviệc không có bất kỳ khoảng trống nào trong việc đánh số. +1
Michael Stramel

1
@Facbed Nó chỉ là một bí danh trên bàn.
Darrel Miller

15
Đối với bất kỳ ai sử dụng Sql Server, hàm RowNumber () được đề cập bởi Mike L là ROW_NUMBER ().
Randomraccoon 6/07/2016

99

Trong T-SQL, tôi sẽ làm:

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
: Hãy mô tả nhiều hơn về giải pháp của bạn. Tham khảo: Cách trả lời
vào

Là truy vấn chọn tại CTE có thể chứa mệnh đề where?
toha

1
@toha Có, nó có thể
KindaTechy

1
Mặc dù bạn nói "Trong T-SQL", điều này hoạt động cho bất kỳ cơ sở dữ liệu nào thực hiện ROW_NUMBERchức năng. Ví dụ: tôi đã sử dụng giải pháp này trong SQLite.
Tony

Nó hoạt động cho postgres sql là tốt. Tôi chỉ phải sử dụng "order by [Prioritise_field] desc"
Phun

35

Điều này hoạt động trên SQL Server 2005 (được chỉnh sửa để phản ánh sự làm rõ của bạn):

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
Tuy nhiên, điều này không hoạt động đối với các hàng trong đó Mục là null. Bạn cần nói "ở đâu (tt.Section là null và t.Section là null) hoặc 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

Bảng có bí danh 'm' là gì?
Phấn

@Chalky đó là lỗi đánh máy, nên được r. đã sửa.
ngày

Làm việc như người ở. Cảm ơn bạn!
Ron Nuni

18

Tôi làm theo cách này:

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;

cập nhật: Ví dụ này của GROUP BY chỉ hoạt động trong MySQL và SQLite, vì các cơ sở dữ liệu đó được cho phép nhiều hơn so với SQL tiêu chuẩn liên quan đến GROUP BY. Hầu hết các triển khai SQL yêu cầu tất cả các cột trong danh sách chọn không phải là một phần của biểu thức tổng hợp cũng nằm trong NHÓM THEO.


1
Nó có hoạt động không? Tôi khá chắc chắn rằng bạn "a.somecolumn không hợp lệ trong danh sách chọn vì nó không có trong hàm tổng hợp hoặc mệnh đề nhóm" cho mỗi cột trong các bài viết ngoại trừ article_id ..
Blorgbeard đã hết

1
Bạn sẽ có thể bao gồm các cột khác phụ thuộc chức năng vào (các) cột có tên trong NHÓM THEO. Các cột không phụ thuộc chức năng là mơ hồ. Nhưng bạn đã đúng, tùy thuộc vào việc triển khai RDBMS. Nó hoạt động trong MySQL nhưng IIRC thất bại trong InterBase / Firebird.
Bill Karwin

1
Điều này sẽ làm việc trong trường hợp mười một hồ sơ hàng đầu cho một phần tất cả có cùng một ngày? Tất cả họ sẽ có số lượng là 11 và kết quả sẽ là một tập hợp trống.
Arth

Không, bạn cần có một số cách phá vỡ mối quan hệ nếu tất cả đều có cùng một ngày. Xem stackoverflow.com/questions/121387/ cấp để biết ví dụ.
Bill Karwin

1
@carlosgg, nếu các bài viết có mối quan hệ nhiều-nhiều với các phần, thì bạn cần phải có một bảng giao nhau để ánh xạ các bài viết đến các phần của chúng. Sau đó, truy vấn của bạn sẽ phải tham gia vào một bảng giao nhau cho mối quan hệ m2m và nhóm theo article_id và phần. Điều đó sẽ giúp bạn bắt đầu, nhưng tôi sẽ không viết ra toàn bộ giải pháp trong một bình luận.
Bill Karwin

16

Nếu chúng tôi sử dụng SQL Server> = 2005, thì chúng tôi có thể giải quyết tác vụ chỉ với một lựa chọn :

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 Tôi thích giải pháp này vì nó đơn giản nhưng bạn có thể giải thích cách sử dụng top 1hoạt động với casecâu lệnh trong order bymệnh đề trả về 0 hoặc 1 không?
Ceres

3
TOP 1 hoạt động với VỚI TIES tại đây. VỚI TIES có nghĩa là khi ORDER BY = 0, thì CHỌN lấy bản ghi này (vì TOP 1) và tất cả những người khác có ORDER BY = 0 (vì VỚI TIES)
Vadim loboda

9

Nếu bạn biết các phần là gì, bạn có thể làm:

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
Đây sẽ là cách dễ nhất để làm điều đó.
Hector Sosa Jr

3
Nhưng điều này sẽ không hiệu quả nếu bạn có 150 hoặc nếu các danh mục thay đổi theo ngày, tuần, v.v.
Rafa Barragan

1
Chắc chắn, nhưng để trích dẫn OP: "Phần là kinh doanh, địa phương và tính năng". Nếu bạn có ba loại tĩnh, đây là cách tốt nhất để làm điều đó.
Blorgbeard ra vào

9

Tôi biết chủ đề này hơi cũ nhưng tôi đã gặp phải một vấn đề tương tự (chọn bài viết mới nhất từ ​​mỗi danh mục) và đây là giải pháp tôi đã đưa ra:

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

Điều này rất giống với giải pháp của Darrel nhưng khắc phục được vấn đề RANK có thể trả về nhiều hàng hơn dự định.


Tại sao nên sử dụng CTE Sir? Có giảm tiêu thụ bộ nhớ?
toha

@toha vì CTE đơn giản và dễ hiểu hơn
Kỹ sư đảo ngược

Câu trả lời chính xác!! Nó có thể được tối ưu hóa bằng cách sử dụng bên trong JOINthay vì LEFT JOIN, vì sẽ không bao giờ có bản ghi TopCategoryArticlesmà không có Articlebản ghi tương ứng .
Kỹ sư đảo ngược

6

Đã thử những điều sau đây và nó cũng hoạt động với mối quan hệ.

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ếu bạn muốn tạo đầu ra được nhóm theo phần, chỉ hiển thị các bản ghi n hàng đầu từ mỗi phần như thế này:

SECTION     SUBSECTION

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

... thì những thứ sau đây sẽ hoạt động khá chung với tất cả các cơ sở dữ liệu SQL. Nếu bạn muốn top 10, chỉ cần thay đổi 2 thành 10 vào cuối truy vấn.

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;

Để thiết lập:

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

Điều này không hoạt động khi tôi chỉ muốn bản ghi đầu tiên cho mỗi phần. Nó loại bỏ tất cả các nhóm phần có nhiều hơn 1 bản ghi. Tôi đã thử bằng cách thay thế <= 2 bằng <= 1
nils

@nils Chỉ có ba giá trị phần: hươu, chó và ngựa. Nếu bạn thay đổi truy vấn thành <= 1, bạn sẽ nhận được một phần phụ cho mỗi phần: Elk / Wapiti của Mỹ cho hươu, Cocker Spaniel cho chó và Appaloosa cho ngựa. Đây cũng là những giá trị đầu tiên trong mỗi phần theo thứ tự abc. Truy vấn có nghĩa là để loại bỏ tất cả các giá trị khác.
Craig

Nhưng khi tôi cố chạy truy vấn của bạn, nó sẽ loại bỏ mọi thứ vì số lượng là> = 1 cho mọi thứ. Nó không bảo tồn tiểu mục 1 cho mỗi phần. Bạn có thể thử chạy truy vấn của mình cho <= 1 và cho tôi biết nếu bạn có phần phụ đầu tiên cho mỗi phần không?
nils

@nils Xin chào, tôi đã tạo lại cơ sở dữ liệu thử nghiệm nhỏ này từ các tập lệnh và chạy truy vấn bằng cách sử dụng <= 1 và nó trả về giá trị phụ đầu tiên từ mỗi phần. Bạn đang sử dụng máy chủ cơ sở dữ liệu nào? Luôn có cơ hội liên quan đến cơ sở dữ liệu của bạn. Tôi chỉ chạy cái này trong MySQL vì nó tiện dụng và nó hoạt động như mong đợi. Tôi khá chắc chắn khi tôi làm điều đó lần đầu tiên (tôi muốn chắc chắn rằng những gì tôi đã đăng thực sự hoạt động mà không cần gỡ lỗi), tôi khá chắc chắn rằng tôi đã làm điều đó bằng cách sử dụng Sybase SQL Anywhere hoặc MS SQL Server.
Craig

nó hoạt động hoàn hảo cho tôi trong mysql. Tôi đã thay đổi một chút truy vấn không chắc tại sao anh ta lại sử dụng <= cho trường varchar trong tiểu mục .. tôi đã thay đổi nó thành và x2.subection = x1.subection
Nakar

4

Có thể nhà điều hành UNION làm việc cho bạn? Có một CHỌN cho mỗi phần, sau đó UNION chúng cùng nhau. Đoán nó sẽ chỉ làm việc cho một số phần cố định mặc dù.


4

Q) Tìm bản ghi TOP X từ mỗi nhóm (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 hàng được chọn.



Câu hỏi là về SQL Server, không phải Oracle.
Craig

2

Trong khi câu hỏi là về SQL Server 2005, hầu hết mọi người đã chuyển sang và nếu họ tìm thấy câu hỏi này, câu trả lời ưa thích trong các tình huống khác là cách sử dụng CROSS APPLYnhư được minh họa trong bài đăng trên blog này .

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

Truy vấn này liên quan đến 2 bảng. Truy vấn của OP chỉ liên quan đến 1 bảng, trong trường hợp giải pháp dựa trên chức năng cửa sổ có thể hiệu quả hơn.


1

Bạn có thể thử phương pháp này. Truy vấn này trả về 10 thành phố đông dân nhất cho mỗi quốc gia.

   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;

Giải pháp này không vượt qua trường hợp thử nghiệm khi chúng tôi có một bảng có bản ghi của một quốc gia có 9 dân số tương tự, ví dụ, nó trả về null thay vì trả lại tất cả 9 bản ghi có sẵn theo thứ tự. Bất kỳ đề nghị để khắc phục vấn đề này?
Mojgan Mazouchi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.