通过查询改进大型时间序列数据集的分组

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)

这在大型数据集上相当慢,据我所知不能使用索引,这就是为什么我将 yearmonthday 作为额外列存储的原因, 这样我就可以使用

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

索引是加速操作的最佳方式。