MYSQL SUM()与GROUP BY和LIMIT

时间:2013-05-06 12:49:16

标签: mysql group-by sum sql-order-by sql-limit

我有这张桌子

CREATE TABLE `votes` (
  `item_id` int(10) unsigned NOT NULL,
  `user_id` int(10) unsigned NOT NULL,
  `vote` tinyint(4) NOT NULL DEFAULT '0',
  PRIMARY KEY (`item_id`,`user_id`),
  KEY `FK_vote_user` (`user_id`),
  KEY `vote` (`vote`),
  KEY `item` (`item_id`),
  CONSTRAINT `FK_vote_item` FOREIGN KEY (`item_id`) REFERENCES `items` (`id`) ON UPDATE CASCADE,
  CONSTRAINT `FK_vote_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

我得到了这个简单的选择

SELECT 
  `a`.`item_id`, `a`.`sum`
FROM
  (SELECT 
    `item_id`, SUM(vote) AS `sum` 
  FROM
    `votes` 
  GROUP BY `item_id`) AS a 
ORDER BY `a`.`sum` DESC
LIMIT 10

现在,只有250行,没有问题,但它正在使用filesort。 vote列包含-101。但是当这个表有数百万或几行时,这会有效吗?

如果我在没有子查询的情况下进行简单查询,则会出现using temporary table

解释给出(查询在0.00170s完成):

id select_type table      type  possible_keys key     key_len ref  rows Extra
1  PRIMARY     <derived2> ALL   NULL          NULL    NULL    NULL 33   Using filesort
2  DERIVED     votes      index NULL          PRIMARY 8       NULL 250

3 个答案:

答案 0 :(得分:2)

不,这对于数百万行来说效率不高。

您必须创建一个支持聚合表,用于存储每个项目的投票数:

CREATE TABLE item_votes
        (
        item_id INT NOT NULL PRIMARY KEY,
        votes UNSIGNED INT NOT NULL,
        upvotes UNSIGNED INT NOT NULL,
        downvotes UNSIGNED INT NOT NULL,
        KEY (votes),
        KEY (upvotes),
        KEY (downvotes)
        )

并在每次投票时更新它:

INSERT
INTO    item_votes (item_id, votes, upvotes, downvotes)
VALUES  (
        $item_id,
        CASE WHEN $upvote THEN 1 ELSE -1 END,
        CASE WHEN $upvote THEN 1 ELSE 0 END,
        CASE WHEN $upvote THEN 0 ELSE 1 END
        )
ON DUPLICATE KEY
UPDATE
SET     votes = votes + VALUES(upvotes) - VALUES(downvotes),
        upvotes = upvotes + VALUES(upvotes),
        downvotes = downvotes + VALUES(downvotes)

然后选择前10票:

SELECT  *
FROM    item_votes
ORDER BY
        votes DESC, item_id DESC
LIMIT   10

有效使用索引。

答案 1 :(得分:1)

  

但是当这个表有数百万或几行时,这会有效吗?

不,它不会。

  

如果我在没有子查询的情况下使其成为更简单的查询,则会出现使用临时表。

可能是因为计划程序会将其转换为您发布的查询:它需要计算总和以便以正确的顺序返回结果。

要快速获取最高投票问题,您需要缓存结果。在项目表中添加分数字段并进行维护(例如使用触发器)。索引它。然后,您将能够使用索引扫描获取前10个分数。

答案 2 :(得分:0)

首先,您不需要子查询,因此您可以将查询重写为:

SELECT `item_id`, SUM(vote) AS `sum` 
FROM `votes`
GROUP BY `item_id`
ORDER BY `a`.`sum` DESC
LIMIT 10

其次,您可以在votes(item_id, vote)上构建索引。然后group by将成为索引扫描。随着表变大,花费时间,但对于合理的数据大小,它应该是可管理的。

最后,通过这种查询结构,您需要对最终order by进行文件排序。这是否有效取决于您拥有的物品数量。如果每个项目平均有一到两票,那么这可能需要一些时间。如果你有一组固定的项目而且只有几百或几千,那么即使数据大小扩大,也不应该成为性能瓶颈。

如果这个摘要确实是你需要的,那么带有摘要表的触发器(如另一个答案中所述)提供了一种更快的检索方法。