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


207

我想在一个查询中返回每个部分的前10条记录。任何人都可以帮忙吗?节是表中的列之一。

数据库是SQL Server2005。我想按输入的日期返回前十名。部分是业务,本地和功能。对于一个特定的日期,我只需要前(10)个业务行(最近输入),前(10)个本地行和前(10)个功能。


这些答案对您有用吗?
Kyle Delaney

3
我想我们永远不会知道...
Denny

已经12年了,我们不知道其中任何一个是否有效。

Answers:


221

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


31
如果您真的只想要前十名,请将其更改为RowNumber()而不是Rank()。那没有关系
迈克L”

3
这可行,但是请注意,如果没有索引的第一个键是RankCriteria ,则查询计划程序可能会将rank()转换为全表排序。在这种情况下,选择不同的部分并交叉申请以选择RankCriteria desc订购的前十名可能会获得更好的里程。
Joe Kearney

好答案!让我几乎完全符合我的需求。我最终选择了DENSE_RANK没有任何间隙的编号。+1
Michael Stramel

1
@Facbed这只是表上的别名。
Darrel Miller 2014年

15
对于使用Sql Server的任何人,Mike L提到的RowNumber()函数都是ROW_NUMBER()。
randomraccoon

99

在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

2
:请对您的解决方案进行更多描述。请参阅:如何回答
askmish

CTE的select查询是否可以包含where子句?
toha 2013年

1
@toha是的,它可以
KindaTechy

1
尽管您说“在T-SQL中”,这对于实现该ROW_NUMBER功能的任何数据库都适用。例如,我在SQLite中使用了此解决方案。
托尼,

它也适用于postgres sql。我只需要使用“由[prioritise_field] 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

2
但是,这对于Section为null的行不起作用。您需要说“哪里(tt.Section为null,t.Section为null)或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

什么是别名为“ m”的表?
Chalky

@Chalky是错字,应该是r。固定。
朗朗

像魅力一样工作。谢谢!
罗恩尼

18

我这样做:

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中工作,因为关于GROUP BY,这些数据库比标准SQL允许得多。大多数SQL实现要求select列表中不属于聚合表达式的所有列也位于GROUP BY中。


1
那样有用吗?我非常确定您会为除了article_id以外的文章中的每一列“在选择列表中a.somecolumn无效,因为它不包含在聚合函数或group by子句中。”
Blorgbeard将于

1
您应该能够包括功能上依赖于GROUP BY中命名的列的其他列。不依赖功能的列是不明确的。但是您是对的,取决于RDBMS的实现。它在MySQL中有效,但IIRC在InterBase / Firebird中失败。
Bill Karwin

1
如果某个部分的前十一个记录都具有相同的日期,则该工作是否可行?它们都将计数为11,结果将为空集。
Arth 2015年

不,如果他们都有相同的日期,则需要有一些打破关系的方法。有关示例,请参见stackoverflow.com/questions/121387/…
比尔·卡温

1
@carlosgg,如果文章与版块之间存在多对多关系,则您需要一个交叉表将文章映射到其版块。然后,您的查询将必须连接到m2m关系的相交表,并按article_id和section分组。那应该使您入门,但是我不会在注释中写出整个解决方案。
Bill Karwin

16

如果我们使用SQL Server> = 2005,则可以只选择一项来解决任务:

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我很喜欢这种解决方案,因为它很简单,但是您能解释一下如何使用子句中返回0或1 top 1case语句order by吗?
谷神星

3
TOP 1与WITH TIES一起使用。WITH TIES表示,当ORDER BY = 0时,SELECT记录(由于TOP 1)以及所有其他ORDER BY = 0(由于WITH TIES)的记录
Vadim Loboda

9

如果您知道这些部分是什么,则可以执行以下操作:

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
这将是最简单的方法。
赫克托·索萨(Hector Sosa Jr)

3
但是,如果你有150个,这将是低效的,如果类别是由天变,周等
拉法·巴拉甘

1
可以,但是引用OP:“部分是业务,本地和功能”。如果您有三个静态类别,这是最好的方法。
Blorgbeard在

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问题,该问题可能返回比预期更多的行。


为什么使用CTE先生?它会减少内存消耗吗?
托哈16/09/13

@toha,因为CTE更简单易懂
工程师,

好答案!可以通过使用inner JOIN而不是进行优化LEFT JOIN,因为TopCategoryArticles没有相应的Article记录就永远不会有记录。
逆向工程师,

6

尝试了以下内容,并且也可以使用联系。

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条记录,如下所示:

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

当我只需要每个部分的第一条记录时,这将不起作用。它消除了具有多个1条记录的所有节组。我尝试通过将<= 2替换为<= 1

@nils只有三个部分值:鹿,狗和马。如果将查询更改为<= 1,则每个部分都有一个小节:“美洲麋鹿/马鹿”代表鹿,“可卡犬”代表狗,“阿帕卢萨”代表马。这些也是每个部分中按字母顺序排列的第一个值。该查询旨在消除所有其他值。
克雷格

但是,当我尝试运行您的查询时,它消除了所有内容,因为所有内容的计数均> = 1。它不会为每个部分保留第一部分。您是否可以尝试对<= 1进行查询,并让我知道您是否获得每个部分的第一部分?
nils

@nils嗨,我确实从脚本中重新创建了这个小的测试数据库,并使用<= 1运行了查询,它从每个部分返回了第一个子部分的值。您正在使用哪个数据库服务器?总是有可能与您选择的数据库有关。我只是在MySQL中运行了它,因为它很方便并且表现出预期。我很确定第一次执行此操作(我想确保发布的内容在没有调试的情况下就可以正常工作),我很确定我是使用Sybase SQL Anywhere或MS SQL Server进行的。
Craig

它在mysql中对我来说非常理想。我稍微更改了一个查询,但不确定为什么他在子节中为什么使用<=表示varchar字段。我将其更改为x2.subsection = x1.subsection
Mahen Nakar

4

可能会在UNION为您操作员的工作量?每个部分都有一个SELECT,然后将它们联合在一起。猜猜它仅适用于固定数量的部分。


4

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



问题是关于SQL Server,而不是Oracle。
克雷格

2

虽然问题是关于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;

当我们有一个表中有一个人口总数为9个国家的记录时,此解决方案不会通过测试用例,例如,它返回null而不是按顺序返回所有9个可用记录。有任何解决此问题的建议吗?
Mojgan Mazouchi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.