为每个类别选择前 10 条记录

问题描述 投票:0回答:15

我想在一次查询中返回每个部分的前 10 条记录。任何人都可以帮助如何做到这一点? Section 是表中的列之一。

数据库是 SQL Server 2005。我想按输入的日期返回前 10 个。部分是业务、本地和功能。对于一个特定的日期,我只想要前 (10) 个业务行(最新条目)、前 (10) 个本地行和前 (10) 个功能。

sql sql-server sql-server-2005 greatest-n-per-group
15个回答
269
投票

如果您使用的是 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 行,Matt 的解决方案可能更适合您。


122
投票

在 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

46
投票
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

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

19
投票

我这样做:

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 实现要求选择列表中不属于聚合表达式的所有列也在 GROUP BY 中。


18
投票

如果我们使用 SQL Server >= 2005,那么我们只需要一个 select 就可以解决这个问题:

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;

15
投票

如果你知道这些部分是什么,你可以这样做:

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

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

这与 Darrel 的解决方案非常相似,但克服了可能返回比预期更多行的 RANK 问题。


8
投票

尝试了以下方法,它也适用于领带。

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

6
投票

如果你想产生按部分分组的输出,只显示每个部分的前 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';

6
投票

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 行选择。



4
投票

UNION 接线员可以为您工作吗?每个部分都有一个 SELECT,然后将它们 UNION 在一起。我猜它只适用于固定数量的部分。


4
投票

虽然问题是关于 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张表。 OP 的查询只涉及 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;

0
投票

注意:我知道 OP 只有 3 个组,但这是许多开发人员已知的普遍问题,并且在 SQL 中没有真正好的解决方案。所以让我告诉你另一种方式。


理论上:

你可以把它写成一个查询。也就是说,形式上是一个查询,但它包含子查询或自连接,这使得它实际上在幕后有多个操作。所以你不妨单独选择每个组。


实践中:

如果你想要一个高性能的解决方案,你需要做更多的工作。假设您有 100 名员工,从 A 到 Z 有 26 座建筑物,人们在建筑物周围移动(进入/离开),并且您需要每座建筑物的最后 5 个事件。

EVENT_ID   EVENT_TIME            EMPOYEE_ID   EVENT_CODE   BUILDING                           
-------------------------------------------------------------------------                                
883691     2023-03-29 11:00:00   92           enter        A                                           
883690     2023-03-29 11:00:21   78           enter        C                                           
883689     2023-03-29 11:00:25   58           enter        A                                           
883688     2023-03-29 11:02:10   22           leave        H                                           
883687     2023-03-29 11:31:42   73           leave        P                                           
...
...

您想避免 26 个查询。

这是您可以做的:

  1. 使用简单的 ORDER BY EVENT_ID DESC(或 EVENT_TIME DESC)编写查询,以获取所有建筑物的最后 N 个事件。
  2. 将 N(限制)设置为一个合理的估计,该估计将包含大多数建筑物的数据,您不需要所有这些数据,但越多越好。假设 LIMIT 5000.
  3. 在应用端处理结果集,看看哪些建筑物没有进入前10。
  4. 对于这些建筑物,运行单独的查询以获得前 10 名。

对于理论家来说,这是一种反模式。但是第一个查询的性能几乎与单个建筑物中的一个查询相同,并且可能会为您带来大部分所需的内容;一些建筑物将丢失,这取决于员工通常如何移动。然后,您可能需要对这些建筑物进行 5 个查询,并在应用程序级别进行一些合并。

简而言之:得到一个几乎完成的结果,然后使它完成。

所以如果你需要性能,这是一种方式。 如果您需要清晰的业务逻辑,那么,请选择任何其他答案。这个很可怕。但是当你追求速度时,你通常需要可怕的技术。

© www.soinside.com 2019 - 2024. All rights reserved.