[英]How Could this Mysql Query be Improved?
该查询试图做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的问题。总而言之,我试图避免大量查询和昂贵的查询。
关于将查询分解并将其移入应用程序以提高查询速度的最佳方法的任何想法?
要回答您的问题,我没有找到有效的方法来“分解”此查询。 您仍然需要确定该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
是否等于常数。 这意味着查询要从单个user_id发布的articles
中识别行,并在该用户连续发布两(或更多)文章时递增row_number,而IN
列表中没有其他user_id发布的中间文章(如按DATE列排序)。 由另一个user_id(在IN列表中)发布的文章将计数器重置为1。
最终结果是,此查询从IN列表中指定的所有用户返回所有文章,但单个user_id(可能在列表中也可能不在该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)
可能会更加有益。
这里有点假设-如果您尝试在给定的用户列表中列出每个用户的最新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
这样做可以避免使用变量。
将表与自身连接起来,并加入用户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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.