通过查询改进大型时间序列数据集的分组
Improving group by query for large time-series dataset
我有一个很大的table,其中包含一整年的时间序列数据,每天有 24 个值(每小时一个),适用于多个客户。
Customer-ID
Date
Value
001
2020-01-01 00:00:00
xx
001
2020-01-01 00:01:00
xx
001
2020-01-01 00:02:00
xx
...
...
...
002
2020-01-01 00:00:00
xx
002
2020-01-01 00:01:00
xx
002
2020-01-01 00:02:00
xx
...
...
...
我目前将整个数据集存储在单个 sqlite table 中,然后我从 python 应用程序中查询,您可以在其中 select 不同形式的可视化(按月每年或按天显示 12 个值,每年显示 365 个值)。
CREATE TABLE "data" (
"index" INTEGER NOT NULL,
"customer_id" INTEGER NOT NULL,
"date" DATETIME NOT NULL,
"value" FLOAT NOT NULL,
"year" INTEGER NOT NULL,
"month" INTEGER NOT NULL,
"day" INTEGER NOT NULL,
PRIMARY KEY("index")
);
CREATE INDEX "idx_data_ym" ON "data" (
"year",
"month"
);
CREATE INDEX "idx_data_ymd" ON "data" (
"year",
"month",
"day"
);
我可以用来显示每月数据的一个天真的查询是
SELECT date, sum(value) FROM data GROUP BY CAST(STRFTIME('%Y', date) AS INTEGER), CAST(STRFTIME('%m', date) AS INTEGER)
这在大型数据集上相当慢,据我所知不能使用索引,这就是为什么我将 year
、month
和 day
作为额外列存储的原因, 这样我就可以使用
SELECT date, sum(value) from data GROUP BY year, month
结果:
Date
Sum
2017-01-01 00:00:00
yy
2017-01-02 00:00:00
yy
..
..
2018-01-01 00:00:00
yy
2018-01-02 00:00:00
yy
..
..
对于小型测试数据集(100 个客户,每个值 24 小时,两年 = 100 * 24 * 365 * 2
= 1.752.000
条记录),第一个查询大约需要 20s
而第二个只需要 1.8s
.
使用 EXPLAIN QUERY PLAN
检查查询,第二个查询使用 idx_data_ym
索引,这是我想要的,而第一个查询不使用索引。
第一个查询的输出 EXPLAIN QUERY PLAN
:
id
parent
notused
detail
6
0
0
SCAN TABLE data
8
0
0
USE TEMP B-TREE FOR GROUP BY
第二个查询的输出:
id
parent
notused
detail
7
0
0
SCAN TABLE data USING INDEX idx_data_ym
现在我想知道,1.8s
可能没问题,但生产中的数据集会更大,这会极大地降低应用程序的速度。按 year, month, day
分组以获得每天的总和值甚至更慢。
有没有办法提高我的查询性能?我对如何聚合数据的理解完全错误吗?
感谢您的帮助!
不需要额外的年月日列。
你可以设置Indexes On Expressions:
CREATE TABLE "data" (
"index" INTEGER NOT NULL,
"customer_id" INTEGER NOT NULL,
"date" TEXT NOT NULL, -- there is no DATETIME data type in SQLite
"value" FLOAT NOT NULL,
PRIMARY KEY("index")
);
CREATE INDEX "idx_data_ym" ON "data"(strftime('%Y-%m', date));
CREATE INDEX "idx_data_ymd" ON "data"(date(date)); -- equivalent of strftime('%Y-%m-%d', date)
对于这些查询:
SELECT STRFTIME('%Y-%m', date) AS year_month,
SUM(value) AS total
FROM data
GROUP BY year_month;
SELECT date(date) AS year_month_day,
SUM(value) AS total
FROM data
GROUP BY year_month_day;
将使用正确的索引。
参见demo。
索引是加速操作的最佳方式。
我有一个很大的table,其中包含一整年的时间序列数据,每天有 24 个值(每小时一个),适用于多个客户。
Customer-ID | Date | Value |
---|---|---|
001 | 2020-01-01 00:00:00 | xx |
001 | 2020-01-01 00:01:00 | xx |
001 | 2020-01-01 00:02:00 | xx |
... | ... | ... |
002 | 2020-01-01 00:00:00 | xx |
002 | 2020-01-01 00:01:00 | xx |
002 | 2020-01-01 00:02:00 | xx |
... | ... | ... |
我目前将整个数据集存储在单个 sqlite table 中,然后我从 python 应用程序中查询,您可以在其中 select 不同形式的可视化(按月每年或按天显示 12 个值,每年显示 365 个值)。
CREATE TABLE "data" (
"index" INTEGER NOT NULL,
"customer_id" INTEGER NOT NULL,
"date" DATETIME NOT NULL,
"value" FLOAT NOT NULL,
"year" INTEGER NOT NULL,
"month" INTEGER NOT NULL,
"day" INTEGER NOT NULL,
PRIMARY KEY("index")
);
CREATE INDEX "idx_data_ym" ON "data" (
"year",
"month"
);
CREATE INDEX "idx_data_ymd" ON "data" (
"year",
"month",
"day"
);
我可以用来显示每月数据的一个天真的查询是
SELECT date, sum(value) FROM data GROUP BY CAST(STRFTIME('%Y', date) AS INTEGER), CAST(STRFTIME('%m', date) AS INTEGER)
这在大型数据集上相当慢,据我所知不能使用索引,这就是为什么我将 year
、month
和 day
作为额外列存储的原因, 这样我就可以使用
SELECT date, sum(value) from data GROUP BY year, month
结果:
Date | Sum |
---|---|
2017-01-01 00:00:00 | yy |
2017-01-02 00:00:00 | yy |
.. | .. |
2018-01-01 00:00:00 | yy |
2018-01-02 00:00:00 | yy |
.. | .. |
对于小型测试数据集(100 个客户,每个值 24 小时,两年 = 100 * 24 * 365 * 2
= 1.752.000
条记录),第一个查询大约需要 20s
而第二个只需要 1.8s
.
使用 EXPLAIN QUERY PLAN
检查查询,第二个查询使用 idx_data_ym
索引,这是我想要的,而第一个查询不使用索引。
第一个查询的输出 EXPLAIN QUERY PLAN
:
id | parent | notused | detail |
---|---|---|---|
6 | 0 | 0 | SCAN TABLE data |
8 | 0 | 0 | USE TEMP B-TREE FOR GROUP BY |
第二个查询的输出:
id | parent | notused | detail |
---|---|---|---|
7 | 0 | 0 | SCAN TABLE data USING INDEX idx_data_ym |
现在我想知道,1.8s
可能没问题,但生产中的数据集会更大,这会极大地降低应用程序的速度。按 year, month, day
分组以获得每天的总和值甚至更慢。
有没有办法提高我的查询性能?我对如何聚合数据的理解完全错误吗?
感谢您的帮助!
不需要额外的年月日列。
你可以设置Indexes On Expressions:
CREATE TABLE "data" (
"index" INTEGER NOT NULL,
"customer_id" INTEGER NOT NULL,
"date" TEXT NOT NULL, -- there is no DATETIME data type in SQLite
"value" FLOAT NOT NULL,
PRIMARY KEY("index")
);
CREATE INDEX "idx_data_ym" ON "data"(strftime('%Y-%m', date));
CREATE INDEX "idx_data_ymd" ON "data"(date(date)); -- equivalent of strftime('%Y-%m-%d', date)
对于这些查询:
SELECT STRFTIME('%Y-%m', date) AS year_month,
SUM(value) AS total
FROM data
GROUP BY year_month;
SELECT date(date) AS year_month_day,
SUM(value) AS total
FROM data
GROUP BY year_month_day;
将使用正确的索引。
参见demo。
索引是加速操作的最佳方式。