【问题标题】:Optimized way to get top n records of each group优化获取每组前 n 条记录的方法
【发布时间】:2018-11-09 07:05:33
【问题描述】:

我需要一个有近 10 万条记录的 mysql 表中每个子类别的前 6 条记录。我尝试了以下 mysql 查询,但我担心它在具有大量记录的表中的性能。

SELECT 
    *
FROM
    (SELECT 
        sub_cat_id,
        title,      
        @rn:=IF(@prev = sub_cat_id, @rn + 1, 1) AS rn,
        @prev:=sub_cat_id AS previd,
        created_date
    FROM
        blog
    WHERE
        type = 'BLOG'
            AND FIND_IN_SET(sub_cat_id, '1,2,8')
            AND created_date <= NOW()
    ORDER BY sub_cat_id DESC , created_date DESC) AS records
WHERE
    rn <= 6

在上述查询中,MySQL 将对 sub_cat_id 为 1,2 和 8 的所有记录进行编号。在外部选择查询中,每个子类别的记录将减少到 6 条记录。

我有以下顾虑

  • 这是为每个子类别获取 6 条记录的更好、最快的方法吗?
  • 此查询是否生成所需的结果。

这是我的博客

+-------- +------------+-------+--------------+
| blog_id | sub_cat_id | title | created_date |
+-------- +------------+-------+--------------+
| 1       | 1          | ABC   | 2018-05-25   |
| 2       | 1          | ABC   | 2018-05-22   |
| 3       | 2          | ABC   | 2018-05-23   |
| 4       | 2          | ABC   | 2018-05-21   |
| 5       | 2          | ABC   | 2018-05-20   |
| 6       | 8          | ABC   | 2018-05-15   |
+-------- +------------+-------+--------------+

【问题讨论】:

    标签: mysql stored-procedures greatest-n-per-group mysql-variables


    【解决方案1】:

    您的方法很好,但您的查询不是。特别是,MySQL 不保证 SELECT 中表达式的求值顺序,因此您不应在一个表达式中分配变量并在另一个表达式中使用它。

    幸运的是,您可以将这些赋值组合成一个表达式:

    SELECT b.*
    FROM (SELECT b.sub_cat_id, b.title,  created_date     
                 (@rn := IF(@sc = b.sub_cat_id, @rn + 1,
                            if(@sc := b.sub_cat_id, 1, 1)
                           )
                 ) as rn
          FROM blog b CROSS JOIN
               (SELECT @sc := -1, @rn := 0) params
          WHERE b.type = 'BLOG' AND
                b.sub_cat_id IN (1, 2, 8) AND
                b.created_date <= NOW()  -- is this really needed?
          ORDER BY b.sub_cat_id DESC, b.created_date DESC) AS records
         ) b
    WHERE rn <= 6;
    

    对于这个查询,您需要索引。我认为这会起作用:type, sub_cat_id, created_date)。不幸的是,group by 仍然需要对数据进行排序。在更新的 MySQL 版本中,我认为您需要在子查询中进行排序,然后再分配 rn

    我确实想知道这个公式是否可以更有效:

    select b.*
    from blogs b
    where b.type = 'BLOG' and
          b.sub_cat_id in (1, 2, 8) and
          b.created_at >= (select b2.created_at
                           from blogs b2
                           where b2.type = b.type and
                                 b2.sub_cat_id = b.sub_cat_id
                           order by b2.created_at desc
                           limit 1 offset 5
                          );
    

    为此,您需要blog(type, sub_cat_id, created_at) 上的索引。

    【讨论】:

    • 如果第二个选项更快,我会有点惊讶。 @Anoop,如果是,请告诉我们。
    • 第二个查询要好很多。但是如果每个子类别只包含 2 条记录,则不会产生任何结果,上述查询中的偏移量 5 将失败。
    • @Anoop 。 . .我不明白你的cmets。查询不工作,或者它更快。哪一个?它怎么不工作?仅当行数不够时?
    • @GordonLinoff 第一个查询对我来说很好,但第二个查询不起作用。我试图更正第二个查询,以便我可以检查哪个更快。我喜欢你的第二个查询,但它不起作用。
    猜你喜欢
    • 2021-09-01
    • 1970-01-01
    • 2014-08-28
    • 2016-11-26
    • 2022-12-07
    相关资源
    最近更新 更多