【问题标题】:Select top 10 records for each category为每个类别选择前 10 条记录
【发布时间】:2010-09-15 16:08:44
【问题描述】:

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

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

【问题讨论】:

    标签: sql sql-server sql-server-2005 greatest-n-per-group


    【解决方案1】:

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

    【讨论】:

    • 如果您真的只想要前 10 名,请将其更改为 RowNumber() 而不是 Rank()。那就没有关系了。
    • 这可行,但请注意,如果没有 first 键为的索引,则查询计划器可能会将 rank() 转换为全表排序排名标准。在这种情况下,您可能会获得更好的里程选择不同的部分并交叉申请以挑选出按 RankCriteria desc 排序的前 10 名。
    • 很好的答案!几乎完全得到了我需要的东西。我最终选择了DENSE_RANK,它在编号上没有任何差距。 +1
    • @Facbed 只是桌子上的别名。
    • 对于任何使用 Sql Server 的人来说,Mike L 提到的 RowNumber() 函数是 ROW_NUMBER()。
    【解决方案2】:

    在 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
    

    【讨论】:

    • :请详细描述您的解决方案。参考:How to Answer
    • CTE的select查询是否可以包含where子句?
    • @toha 可以
    • 尽管您说“在 T-SQL 中”,但这适用于任何实现 ROW_NUMBER 函数的数据库。例如,我在 SQLite 中使用过这个解决方案。
    • 它也适用于 postgres sql。我只需要使用“order by [prioritise_field] desc”
    【解决方案3】:
    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
    

    【讨论】:

    • 工作就像一个魅力。谢谢!
    • 如果 DateEntered 出现平局怎么办?
    • @Yiping 未定义。任何一排都可能赢。这取决于许多情况,并且可能会有所不同。您可以通过添加更多列来使订单更具体,例如ORDER BY r.DateEntered, r.ID,但这完全取决于您的特定任务。
    • 谢谢,我认为在你的答案中使用 RANK() 会使其工作。
    • @Yiping 的问题是关于每个类别的前 10 行,RANK() 可以产生更多。但是如果你需要前 10 名的关系,RANK() 绝对是最好的选择。
    【解决方案4】:

    这适用于 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
    

    【讨论】:

    • 这不适用于 Section 为空的行。你需要说“哪里(tt.Section 为空,t.Section 为空)或 tt.Section = t.Section”
    【解决方案5】:

    我是这样做的:

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

    【讨论】:

    • 这行得通吗?我很确定你会“a.somecolumn 在选择列表中无效,因为它不包含在聚合函数或 group by 子句中”对于文章中的每一列,除了 article_id..
    • 您应该能够包含在功能上依赖于 GROUP BY 中命名的列的其他列。不依赖于功能的列是不明确的。但你是对的,这取决于 RDBMS 的实现。它在 MySQL 中有效,但 IIRC 在 InterBase/Firebird 中失败。
    • 如果一个部分的前 11 条记录都具有相同的日期,这是否有效?它们的计数都是 11,结果将是一个空集。
    • 不,如果他们都有相同的日期,你需要有一些方法来打破关系。有关示例,请参见 stackoverflow.com/questions/121387/…。
    • @carlosgg,如果文章与部分具有多对多关系,那么您需要有一个交集表来将文章映射到它们的部分。然后,您的查询必须加入 m2m 关系的交集表,并按 article_id 和部分分组。这应该让你开始,但我不会在评论中写出整个解决方案。
    【解决方案6】:

    如果我们使用 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;
    

    【讨论】:

    • +1 我喜欢这个解决方案,因为它很简单,但你能解释一下在order by 子句中使用top 1 和返回0 或1 的case 语句是如何工作的吗?
    • TOP 1 在这里与 WITH TIES 一起使用。 WITH TIES 表示当 ORDER BY = 0 时,SELECT 获取这条记录(因为 TOP 1)和所有其他 ORDER BY = 0 的记录(因为 WITH TIES)
    【解决方案7】:

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

    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
    

    【讨论】:

    • 这是最简单的方法。
    • 但是如果你有 150 个或者如果类别是按天、周等变化的,这将是低效的。
    • 当然,但是引用 OP:“部分是业务、本地和功能”。如果您有三个静态类别,这是最好的方法。
    【解决方案8】:

    我知道这个帖子有点老了,但我刚刚遇到了一个类似的问题(从每个类别中选择最新的文章),这是我想出的解决方案:

    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 先生?是减少内存消耗吗?
    • @toha 因为 CTE 更简单、更容易理解
    • 很好的答案!可以通过使用内部JOIN 而不是LEFT JOIN 对其进行优化,因为如果没有相应的Article 记录,则永远不会有TopCategoryArticles 的记录。
    【解决方案9】:

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

    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
    

    【讨论】:

    • 您好,在我在视图中添加聚合度量之前,查询对我来说很好。 SELECT rs.Field1,rs.Field2,rs.measure FROM ( SELECT Field1,Field2, sum(cast(measure as INT)) over(partition by section) as agg_measure, ROW_NUMBER() OVER (Partition BY Section ORDER BY agg_measure DESC) AS Rank FROM table ) rs WHERE Rank
    【解决方案10】:

    如果要生成按部分分组的输出,则仅显示每个部分的前 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 条记录的部分组。我尝试将
    • @nils 只有三个部分值:鹿、狗和马。如果将查询更改为 意味着消除所有其他值。
    • 但是当我尝试运行您的查询时,它会消除所有内容,因为所有内容的计数 >=1。它不保留每个部分的第一个小节。您能否尝试运行
    • @nils 嗨,我确实从脚本中重新创建了这个小测试数据库并使用
    • 它在 mysql 中非常适合我。我更改了一个查询,不知道为什么他在小节中使用
    【解决方案11】:

    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。
    【解决方案12】:

    UNION 运算符可能适合您吗?每个部分有一个 SELECT,然后将它们联合在一起。不过,我猜它只适用于固定数量的部分。

    【讨论】:

      【解决方案13】:

      虽然问题是关于 SQL Server 2005,但大多数人已经继续前进,如果他们确实找到了这个问题,那么在其他情况下可能首选的答案是 using CROSS APPLY as illustrated in this blog post。

      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 个表,在这种情况下,基于窗口函数的解决方案可能更有效。

      【讨论】:

        【解决方案14】:

        您可以尝试这种方法。 此查询返回每个国家/地区的 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 个可用记录。有解决此问题的建议吗?
        猜你喜欢
        • 2019-01-17
        • 1970-01-01
        • 1970-01-01
        • 2020-03-12
        • 2011-01-24
        • 1970-01-01
        • 2011-04-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多