2

这个查询试图做一些mysql不容易做的事情,即限制每组的行数。将列表user_id's传递给查询,并返回一些,但组需要限制为每组 4 行。该查询有效,但根据 Sequel Pro 的说法,200-500 毫秒的速度有点慢。

请在标记前继续阅读!!

SELECT id, user_id, article_id, row_number
FROM (
    SELECT a2.id, a2.user_id, a2.post_id,
        @num:= if(@group = a2.user_id, @num + 1, 1) as row_number
    FROM (
        SELECT a1.id, a1.user_id, a1.post_id
        FROM articles as a1
        WHERE a1.user_id IN (3,14,1,2,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,19,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,38,39,13,114,1111,12,223,2234,225,226,227,228,229,2210)
        ORDER BY a1.date DESC
    ) as a2, 
    (SELECT @num := 0) t
) as f
WHERE row_number <= 4;

这个查询的解释是:

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   PRIMARY <derived2>  ALL         NULL    NULL    NULL    NULL    10516   Using where
2   DERIVED <derived4>  system      NULL    NULL    NULL    NULL    1   
2   DERIVED <derived3>  ALL         NULL    NULL    NULL    NULL    10516   
4   DERIVED NULL        NULL        NULL    NULL    NULL    NULL    NULL    No tables used
3   DERIVED s1          ALL         Reco... NULL    NULL    NULL    1180931 Using filesort

我曾考虑将其分解为多个查询,但我似乎仍然遇到将每个组结果限制为 4 个的问题。总而言之,我试图避免大量查询和昂贵的查询。

关于通过将其分解并将其中的一些移动到应用程序中来提高此查询速度的最佳方法的任何想法?

4

3 回答 3

1

要回答您的问题,我没有看到任何有效的方法来“分解”这个查询。您仍然需要确定来自那个 user_id (@group) 的文章是否按日期连续,没有来自其他 user_id 之一的干预帖子。将所有行按日期排序在一起将是最好的方法。

如果要消除的行数是行的一个很大的子集,那么在客户端过滤这些将需要向客户端发送更大的结果集。但是,如果只是一小部分行被过滤掉,那么这使得将所有行(对于列表中的所有用户)传输到客户端进行处理更具吸引力。

SELECT a.id
     , a.user_id
     , a.post_id
  FROM articles a
 WHERE a.user_id IN (3,14,1,2,3,4,5,6,7,8,9,10,11,12,...)
 ORDER BY a.date DESC

然后客户端可以获取行,检查该单个 user_id (@group) 的连续行序列,并忽略第五、第六等行,直到找到具有不同 user_id 的行。

如果结果集的规格不同,则有可能将其分解。但是现在编写查询的方式,需要组合来自任何“分解”查询的结果集,以便获得当前查询当前返回的相同结果集。


(此查询与 Marc B 标记为可能重复的问题中的查询有很大不同。)

这是一个奇怪的结果集;我们@group在语句中看不到任何被赋值的地方,所以大概是在执行该语句之前设置的。所以,表达式

@group = a2.user_id

测试 是否user_id等于常数。这意味着查询正在识别articles由单个 user_id 发布的行,并在该用户连续发布两篇(或更多)文章时递增 row_number,IN列表中没有任何其他 user_id 发布的干预文章(按顺序日期列)。另一个 user_id(在 IN 列表中)发布的文章会将计数器重置为 1。

最终结果是,此查询将返回 IN 列表中指定的所有用户的所有文章,除了单个 user_id(可能在列表中也可能不在列表中)。只要有五个或更多文章由该单个常量连续发布user_id,在 IN 列表中没有来自另一个 user_id 的干预文章...每当发生这种情况时,查询仅保留来自该指定 user_id 的前四(最新四)行连续文章。

如果该date列是 DATE 数据类型,没有时间组件,则更有可能您将拥有多个具有相同日期的行。并且没有指定超出date列的顺序,因此结果集是不确定的。(也就是说,同一组行的多个序列可以满足 ORDER BY。)它也与 DATETIME 不确定,但如果这些值中的大多数都包含唯一的时间分量(即不是常量,例如午夜),那么这不太可能是一个问题。

奇怪的是,同一组行可以以两种方式排序,并给出不同的结果。假设@group 标识用户“abc”:

Date       user   id        Date       user   id
---------- ------ --        ---------- ------ --
2103-07-22 abc     1        2103-07-22 abc     1
2103-07-22 abc     2        2103-07-22 abc     2
2103-07-22 abc     3        2103-07-22 abc     3
2103-07-22 EFGHI   4        2103-07-22 abc     5
2103-07-22 abc     5        2103-07-22 abc     6
2103-07-22 abc     6        2103-07-22 abc     7
2103-07-22 abc     7        2103-07-22 EFGHI   4

7 rows selected.            5 rows selected.

两个结果集都与规范一致,因此都可以返回。

返回这样的结果集没有任何问题。这有点奇怪。


在性能方面,具有前导列的索引(user_id)可能适用于 WHERE 子句中的谓词,如果这会消除大部分行。

或者,具有前导列的索引(date,user_id)可能更合适,因为 MySQL 可以避免“使用文件排序”操作,并按日期降序检索行,然后在访问行时过滤掉带有 user_id 谓词的行.

实际上,列上的覆盖索引(date, user_id, post_id, id)可能更有益。

于 2013-07-22T20:31:00.973 回答
0

这里有点假设 - 如果您尝试为给定用户列表中的每个用户列出最新的 4 篇文章,我认为您的查询可能会更好:

SET @gr=0, @row=0;
SELECT 
    id,user_id,post_id,row_number
FROM
    (SELECT 
        id,
            user_id,
            post_id,
            @row:=if(user_id <> @gr, 0, @row + 1) as row_number,
            @gr:=user_id
    FROM
        articles
    WHERE
        user_id IN (3 , 14, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 38, 39, 13, 114, 1111, 12, 223, 2234, 225, 226, 227, 228, 229, 2210)
    ORDER BY user_id , date DESC) as a1
WHERE
    row_number < 4
于 2013-07-22T21:52:11.077 回答
0

可能可以避免使用变量。

将表格与自身连接,加入用户 ID 和日期,查找所有日期大于或相同的文章。然后得到你真正想要的字段分组的匹配文章的数量,并丢弃那些计数超过4的。

没有经过这样的测试。

SELECT a1.id, a1.user_id, a1.post_id, COUNT(a1_plus.id) AS other_count
FROM articles as a1
INNER JOIN articles a1_plus
ON a1.user_id = a1_plus.user_id
AND a1.date <= a1_plus.date
WHERE a1.user_id IN (3,14,1,2,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,19,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,38,39,13,114,1111,12,223,2234,225,226,227,228,229,2210)
GROUP BY a1.id, a1.user_id, a1.post_id
HAVING other_count <= 4
于 2014-10-06T15:08:23.237 回答