MYSQL在由另一列排序的列中选择特定数量的重复值

Posted

技术标签:

【中文标题】MYSQL在由另一列排序的列中选择特定数量的重复值【英文标题】:MYSQL Select specific amount of duplicate values in column sorted by another column 【发布时间】:2019-01-12 00:47:27 【问题描述】:
SELECT notes.* FROM notes 
WHERE notes.id IN (
SELECT T1.id 
FROM notes as T1 
WHERE ( 
    SELECT COUNT(*)
    FROM notes as T2 
    WHERE T2.category_id = T1.category_id 
    AND T2.created_at > T1.created_at 
    AND T1.user_id = T2.user_id
) < N
) 
AND user_id = 2

此查询在按 created_at 排序的每个 category_id 中选择 N 行。 一切正常,直到:

1) 结果数 > 50,因为性能下降是线性的或更差:即使有索引,200 行也需要 2.6 秒。

2) 结果中显示了几个相等的 created_at 值。在这种情况下,您将在类别中获得超过 N 行。

主要问题是如何优化此查询,或者可以编写另一个具有相同功能的查询?特定 user_id 的 1000 行所需的性能为 0.5 秒。 第 2 点是可选的。性能是主要问题。 http://sqlfiddle.com/#!9/aa713f/3

解释:

1   PRIMARY notes   
NULL
ref PRIMARY,user_id user_id 4   const   654 100.00  
NULL

1   PRIMARY T1  
NULL
eq_ref  PRIMARY PRIMARY 4   admin_bt.notes.id   1   100.00  Using where 
3   DEPENDENT SUBQUERY  T2  
NULL
ref category_id,created_at,user_id,catcrbabusr  catcrbabusr 4   admin_bt.T1.category_id 1148    3.33    Using where; Using index    

【问题讨论】:

你能提供相同的解释 1 PRIMARY notes NULL ref PRIMARY,user_id user_id 4 const 654 100.00 NULL 1 PRIMARY T1 NULL eq_ref PRIMARY PRIMARY 4 admin_bt.notes.id 1 100.00 使用 where 3 DEPENDENT SUBQUERY T2 NULL ref category_id,created_at,user_id ,catcrbabusr catcrbabusr 4 admin_bt.T1.category_id 1148 3.33 使用where;使用索引 请相应地编辑您的问题 【参考方案1】:

我不确定我是否完全理解您查询的逻辑,但以下查询至少会产生相同的结果(而且肯定会用更少的时间):

这假设在 (user_id, category_id,created_at) 上有一个索引

SELECT x.* 
  FROM notes x
  JOIN 
     ( SELECT user_id
            , category_id
            , MAX(created_at) created_at
         FROM notes
        WHERE user_id IN(2)
        GROUP
           BY user_id
            , category_id
     ) y
    ON y.user_id = x.user_id
   AND y.category_id = x.category_id
   AND y.created_at = x.created_at;

这是您可能想尝试的另一个想法...

SELECT id
     , created_at
     , user_id
     , category_id
  FROM 
     ( SELECT x.*
            , CASE WHEN @prev = category_id THEN @i:=@i+1 ELSE @i:=1 END i
            , @prev := category_id
         FROM notes x
            , (SELECT @prev:=null,@i:=0) vars
        WHERE user_id = 2
        ORDER  
           BY category_id
            , created_at
      ) n
  WHERE i <= 2;

【讨论】:

看不到在哪里可以设置每个类别的行数。我认为这是因为没有这样对我很重要的功能。可能是我没有很好地解释这一点。我的查询中的 N 是结果中应显示的每个类别的行数。对于 N = 1,我的查询将产生与您相同的结果,只是慢得多。您可以在我的 sqlfiddle 链接中设置 N = 2 并看到响应中有 2 行 category_id = 6。您的查询在 N = 1 时仍然可能有用,因为性能超出了任何预期。感谢您的回复。 所以最终,您会想要一个返回给定用户每个类别的前 n 行最近的行的查询? user_id 只是一个例子。范围无关紧要,因为我可以弄清楚如何在任何查询中设置它。 category_id 和 created_at 是重要的部分,因为我不需要每个类别的随机 N 行,而只需要基于 created_at 的第一个或最后一个。 第二个查询速度很快,工作正常,甚至在某种程度上(尚未正确测试)解决了每个类别超过 N 个结果且 created_at 值相等的问题。这就是答案。谢谢。【参考方案2】:

相关查询自然很昂贵,尤其是在遍历大量第一级行时。它们通常不适合 OLTP。它们可以用于批处理。

如果您需要向在线页面显示此响应,那么也许您应该将结果缓存在您的应用程序中并定期刷新(每 10 分钟一次?)。

无论如何,即使有索引,这个查询也有可能会随着表的增长而变得越来越慢。

另一种选择是定期预处理子查询并将其存储在表中。然后查询它。此查询适用于快速回复。我不知道 mysql 是否有它,但 PostgreSQL 有“物化视图”,就是为了这个目的(可以按需刷新)。

【讨论】:

(mysql 目前没有)

以上是关于MYSQL在由另一列排序的列中选择特定数量的重复值的主要内容,如果未能解决你的问题,请参考以下文章

如何计算另一列中特定值的列的平均值?

使用 dplyr [重复] 有条件地将一列中的值替换为另一列中的值

在 SQL 中,我可以在另一列中获取与它们没有关联的特定值的列中的值吗?

计算由另一列值分组的列值在 pandas 数据框中的共现

计算重复数量并将它们放在数据框的列中

从表中选择行,其中具有相同 id 的另一个表中的行在另一列中具有特定值