当前位置: 首页 > 面试题库 >

最佳查询以在MySQL中获取累积和

宰父学
2023-03-14
问题内容

什么是在MySQL中获取累计金额的“正确”查询?

我有一张表格,其中保存有关文件的信息,一个列列表包含文件的大小(以字节为单位)。(实际文件保存在磁盘上的某个位置)

我想要这样的累积文件大小:

+------------+---------+--------+----------------+
| fileInfoId | groupId | size   | cumulativeSize |
+------------+---------+--------+----------------+
|          1 |       1 | 522120 |         522120 |
|          2 |       2 | 316042 |         316042 |
|          4 |       2 | 711084 |        1027126 |
|          5 |       2 | 697002 |        1724128 |
|          6 |       2 | 663425 |        2387553 |
|          7 |       2 | 739553 |        3127106 |
|          8 |       2 | 700938 |        3828044 |
|          9 |       2 | 695614 |        4523658 |
|         10 |       2 | 744204 |        5267862 |
|         11 |       2 | 609022 |        5876884 |
|        ... |     ... |    ... |            ... |
+------------+---------+--------+----------------+
20000 rows in set (19.2161 sec.)

现在,我使用以下查询来获得以上结果

SELECT
  a.fileInfoId
, a.groupId
, a.size
, SUM(b.size) AS cumulativeSize
FROM fileInfo AS a
LEFT JOIN fileInfo AS b USING(groupId)
WHERE a.fileInfoId >= b.fileInfoId
GROUP BY a.fileInfoId
ORDER BY a.groupId, a.fileInfoId

但是,我的解决方案非常慢。(大约19秒,没有缓存)。

解释给出以下执行细节

+----+--------------+-------+-------+-------------------+-----------+---------+----------------+-------+-------------+
| id | select_type  | table | type  | possible_keys     | key       | key_len | ref            | rows  | Extra       |
+----+--------------+-------+-------+-------------------+-----------+---------+----------------+-------+-------------+
|  1 | SIMPLE       |     a | index | PRIMARY,foreignId | PRIMARY   |       4 | NULL           | 14905 |             |
|  1 | SIMPLE       |     b | ref   | PRIMARY,foreignId | foreignId |       4 | db.a.foreignId |    36 | Using where |
+----+--------------+-------+-------+-------------------+-----------+---------+----------------+-------+-------------+

我的问题是:

如何优化上述查询?

更新
我已经更新了问题,以提供表结构和使用20,000条记录测试数据填充表的过程。

CREATE TABLE `fileInfo` (
  `fileInfoId` int(10) unsigned NOT NULL AUTO_INCREMENT
, `groupId` int(10) unsigned NOT NULL
, `name` varchar(128) NOT NULL
, `size` int(10) unsigned NOT NULL
, PRIMARY KEY (`fileInfoId`)
, KEY `groupId` (`groupId`)
) ENGINE=InnoDB;

delimiter $$
DROP PROCEDURE IF EXISTS autofill$$
CREATE PROCEDURE autofill()
BEGIN
    DECLARE i INT DEFAULT 0;
    DECLARE gid INT DEFAULT 0;
    DECLARE nam char(20);
    DECLARE siz INT DEFAULT 0;
    WHILE i < 20000 DO
        SET gid = FLOOR(RAND() * 250);
        SET nam = CONV(FLOOR(RAND() * 10000000000000), 20, 36);
        SET siz = FLOOR((RAND() * 1024 * 1024));
        INSERT INTO `fileInfo` (`groupId`, `name`, `size`) VALUES(gid, nam, siz);
        SET i = i + 1;
    END WHILE;
END;$$
delimiter ;

CALL autofill();

问题答案:

您可以使用一个变量-它比任何联接都快得多:

SELECT
    id,
    size,
    @total := @total + size AS cumulativeSize,
FROM table, (SELECT @total:=0) AS t;

这是在运行Debian 5.0并具有128MB RAM的奔腾III上的快速测试案例:

创建表:

DROP TABLE IF EXISTS `table1`;

CREATE TABLE `table1` (
    `id` int(11) NOT NULL auto_increment,
    `size` int(11) NOT NULL,
    PRIMARY KEY  (`id`)
) ENGINE=InnoDB;

填写20,000个随机数:

DELIMITER //
DROP PROCEDURE IF EXISTS autofill//
CREATE PROCEDURE autofill()
BEGIN
    DECLARE i INT DEFAULT 0;
    WHILE i < 20000 DO
        INSERT INTO table1 (size) VALUES (FLOOR((RAND() * 1000)));
        SET i = i + 1;
    END WHILE;
END;
//
DELIMITER ;

CALL autofill();

检查行数:

SELECT COUNT(*) FROM table1;

+----------+
| COUNT(*) |
+----------+
|    20000 |
+----------+

运行累积总数查询:

SELECT
    id,
    size,
    @total := @total + size AS cumulativeSize
FROM table1, (SELECT @total:=0) AS t;

+-------+------+----------------+
|    id | size | cumulativeSize |
+-------+------+----------------+
|     1 |  226 |            226 |
|     2 |  869 |           1095 |
|     3 |  668 |           1763 |
|     4 |  733 |           2496 |
...
| 19997 |  966 |       10004741 |
| 19998 |  522 |       10005263 |
| 19999 |  713 |       10005976 |
| 20000 |    0 |       10005976 |
+-------+------+----------------+
20000 rows in set (0.07 sec)

更新

我错过了原始问题中的groupId分组,这肯定使事情变得有些棘手。然后,我写了一个使用临时表的解决方案,但我不喜欢它-混乱且过于复杂。我走了,做了更多的研究,并且想出了更简单,更快捷的方法。

我不能说所有这鈥攊ñ其实功劳,我可以勉强主张任何可言,因为它仅仅是修改后的版本仿效行数从常见的MySQL查询。

它非常简单,优雅且非常快速:

SELECT fileInfoId, groupId, name, size, cumulativeSize
FROM (
    SELECT
        fileInfoId,
        groupId,
        name,
        size,
        @cs := IF(@prev_groupId = groupId, @cs+size, size) AS cumulativeSize,
        @prev_groupId := groupId AS prev_groupId
    FROM fileInfo, (SELECT @prev_groupId:=0, @cs:=0) AS vars
    ORDER BY groupId
) AS tmp;

SELECT ... AS tmp如果您不介意prev_groupID返回列,则可以删除外部。我发现没有它,它的运行速度会稍快一些。

这是一个简单的测试用例:

INSERT INTO `fileInfo` VALUES
( 1, 3, 'name0', '10'),
( 5, 3, 'name1', '10'),
( 7, 3, 'name2', '10'),
( 8, 1, 'name3', '10'),
( 9, 1, 'name4', '10'),
(10, 2, 'name5', '10'),
(12, 4, 'name6', '10'),
(20, 4, 'name7', '10'),
(21, 4, 'name8', '10'),
(25, 5, 'name9', '10');

SELECT fileInfoId, groupId, name, size, cumulativeSize
FROM (
    SELECT
        fileInfoId,
        groupId,
        name,
        size,
        @cs := IF(@prev_groupId = groupId, @cs+size, size) AS cumulativeSize,
        @prev_groupId := groupId AS prev_groupId
    FROM fileInfo, (SELECT @prev_groupId := 0, @cs := 0) AS vars
    ORDER BY groupId
) AS tmp;

+------------+---------+-------+------+----------------+
| fileInfoId | groupId | name  | size | cumulativeSize |
+------------+---------+-------+------+----------------+
|          8 |       1 | name3 |   10 |             10 |
|          9 |       1 | name4 |   10 |             20 |
|         10 |       2 | name5 |   10 |             10 |
|          1 |       3 | name0 |   10 |             10 |
|          5 |       3 | name1 |   10 |             20 |
|          7 |       3 | name2 |   10 |             30 |
|         12 |       4 | name6 |   10 |             10 |
|         20 |       4 | name7 |   10 |             20 |
|         21 |       4 | name8 |   10 |             30 |
|         25 |       5 | name9 |   10 |             10 |
+------------+---------+-------+------+----------------+

这是20,000行表中最后几行的示例:

|      19481 |     248 | 8CSLJX22RCO | 1037469 |       51270389 |
|      19486 |     248 | 1IYGJ1UVCQE |  937150 |       52207539 |
|      19817 |     248 | 3FBU3EUSE1G |  616614 |       52824153 |
|      19871 |     248 | 4N19QB7PYT  |  153031 |       52977184 |
|        132 |     249 | 3NP9UGMTRTD |  828073 |         828073 |
|        275 |     249 | 86RJM39K72K |  860323 |        1688396 |
|        802 |     249 | 16Z9XADLBFI |  623030 |        2311426 |
...
|      19661 |     249 | ADZXKQUI0O3 |  837213 |       39856277 |
|      19870 |     249 | 9AVRTI3QK6I |  331342 |       40187619 |
|      19972 |     249 | 1MTAEE3LLEM | 1027714 |       41215333 |
+------------+---------+-------------+---------+----------------+
20000 rows in set (0.31 sec)


 类似资料:
  • 1. 删除由Angular组件创建的Host HTML元素选择器 2.判断为空

  • 问题内容: 我有一个看起来像这样的表: 我想添加一个新的列,称为cumulative_sum,因此表如下所示: 是否有可以轻松完成此操作的MySQL更新语句?做到这一点的最佳方法是什么? 问题答案: 如果性能是一个问题,则可以使用MySQL变量: 或者,您可以删除该列并在每个查询中对其进行计算: 这以运行方式计算运行总和:)

  • 问题内容: 如果我有一个表列,,, 并且我想运行一个sql查询以获取数据集中最早的记录。 您可以在查询中执行此操作,还是需要在事实之后循环? 我想获取该记录的所有字段。 问题答案: 如果您只想要日期: 如果您需要所有信息: 尽可能避免循环。循环通常会导致游标,游标几乎从来没有必要,而且常常效率很低。

  • 问题内容: 我想将所有mysql表的col名称放入php数组中吗? 是否对此有疑问? 问题答案: 最好的方法是使用INFORMATION_SCHEMA元数据虚拟数据库。特别是INFORMATION_SCHEMA.COLUMNS表… 它非常强大,可以为您提供大量信息,而无需解析文本(例如列类型,列是否可为空,最大列大小,字符集等)… 哦,这是标准的SQL(这是MySQL的特定扩展名)… 有关表之间的

  • 问题内容: 我知道有一些与此相关的帖子,但是我的情况有些不同,因此我希望获得一些帮助。 我需要从数据库中提取一些数据,这些数据是每天交互的累积计数。目前这就是我所拥有的 这样的输出接近我想要的,但不完全是我所需要的。我遇到的问题是日期与发生互动的时分秒存储在一起,因此group by不能将天分组在一起。 这就是输出的样子。http://screencast.com/t/N1KFNFyil 12月2

  • 问题内容: 我有一种情况,我必须在共享主机提供商上更新网站。该站点具有CMS。使用FTP上传CMS的文件非常简单。 我还必须导入一个大的数据库文件(相对于PHP脚本而言)(大约2-3 MB未压缩)。Mysql已关闭,无法从外部访问,因此我必须使用FTP上传文件,然后启动PHP脚本将其导入。可悲的是,我无权访问命令行功能,因此必须使用本地PHP进行解析和查询。我也不能使用LOAD DATA INFI