填充时间序列数据中的空缺
在使用时间序列数据时,由于缺少数据或无活动,数据中可能会存在空缺。通常,我们在查询数据时不想让这些空缺存在。在这种情况下,WITH FILL 子句会派上用场。本指南讨论如何使用 WITH FILL 填充时间序列数据中的空缺。
设置
假设我们有一个表,用于存储由 GenAI 图像服务生成的图像的元数据
CREATE TABLE images
(
`id` String,
`timestamp` DateTime64(3),
`height` Int64,
`width` Int64,
`size` Int64
)
ENGINE = MergeTree
ORDER BY (size, height, width);
让我们导入一些记录
INSERT INTO images VALUES (1088619203512250448, '2023-03-24 00:24:03.684', 1536, 1536, 2207289);
INSERT INTO images VALUES (1088619204040736859, '2023-03-24 00:24:03.810', 1024, 1024, 1928974);
INSERT INTO images VALUES (1088619204749561989, '2023-03-24 00:24:03.979', 1024, 1024, 1275619);
INSERT INTO images VALUES (1088619206431477862, '2023-03-24 00:24:04.380', 2048, 2048, 5985703);
INSERT INTO images VALUES (1088619206905434213, '2023-03-24 00:24:04.493', 1024, 1024, 1558455);
INSERT INTO images VALUES (1088619208524431510, '2023-03-24 00:24:04.879', 1024, 1024, 1494869);
INSERT INTO images VALUES (1088619208425437515, '2023-03-24 00:24:05.160', 1024, 1024, 1538451);
按桶查询
我们将探索 2023 年 3 月 24 日 00:24:03 和 00:24:04 之间创建的图像,所以让我们为这些时间点创建一些参数
SET param_start = '2023-03-24 00:24:03',
param_end = '2023-03-24 00:24:04';
接下来,我们将编写一个查询,将数据分组到 100 毫秒的桶中,并返回在该桶中创建的图像数量
SELECT
toStartOfInterval(timestamp, toIntervalMillisecond(100)) AS bucket,
count() AS count
FROM MidJourney.images
WHERE (timestamp >= {start:String}) AND (timestamp <= {end:String})
GROUP BY ALL
ORDER BY bucket ASC
┌──────────────────bucket─┬─count─┐
│ 2023-03-24 00:24:03.600 │ 1 │
│ 2023-03-24 00:24:03.800 │ 1 │
│ 2023-03-24 00:24:03.900 │ 1 │
│ 2023-03-24 00:24:04.300 │ 1 │
│ 2023-03-24 00:24:04.400 │ 1 │
│ 2023-03-24 00:24:04.800 │ 1 │
└─────────────────────────┴───────┘
结果集仅包含创建了图像的桶,但对于时间序列分析,我们可能希望返回每个 100 毫秒的桶,即使它没有任何条目。
WITH FILL
我们可以使用 WITH FILL 子句来填充这些空缺。我们还将指定 STEP,即要填充的空缺的大小。对于 DateTime 类型,默认值为 1 秒,但我们希望填充 100 毫秒长度的空缺,所以让我们将 100 毫秒的间隔作为我们的步长值
SELECT
toStartOfInterval(timestamp, toIntervalMillisecond(100)) AS bucket,
count() AS count
FROM MidJourney.images
WHERE (timestamp >= {start:String}) AND (timestamp <= {end:String})
GROUP BY ALL
ORDER BY bucket ASC
WITH FILL
STEP toIntervalMillisecond(100);
┌──────────────────bucket─┬─count─┐
│ 2023-03-24 00:24:03.600 │ 1 │
│ 2023-03-24 00:24:03.700 │ 0 │
│ 2023-03-24 00:24:03.800 │ 1 │
│ 2023-03-24 00:24:03.900 │ 1 │
│ 2023-03-24 00:24:04.000 │ 0 │
│ 2023-03-24 00:24:04.100 │ 0 │
│ 2023-03-24 00:24:04.200 │ 0 │
│ 2023-03-24 00:24:04.300 │ 1 │
│ 2023-03-24 00:24:04.400 │ 1 │
│ 2023-03-24 00:24:04.500 │ 0 │
│ 2023-03-24 00:24:04.600 │ 0 │
│ 2023-03-24 00:24:04.700 │ 0 │
│ 2023-03-24 00:24:04.800 │ 1 │
└─────────────────────────┴───────┘
我们可以看到,空缺已用 count 列中的 0 值填充。
WITH FILL...FROM
然而,时间范围的开始处仍然存在一个空缺,我们可以通过指定 FROM 来修复它
SELECT
toStartOfInterval(timestamp, toIntervalMillisecond(100)) AS bucket,
count() AS count
FROM MidJourney.images
WHERE (timestamp >= {start:String}) AND (timestamp <= {end:String})
GROUP BY ALL
ORDER BY bucket ASC
WITH FILL
FROM toDateTime64({start:String}, 3)
STEP toIntervalMillisecond(100);
┌──────────────────bucket─┬─count─┐
│ 2023-03-24 00:24:03.000 │ 0 │
│ 2023-03-24 00:24:03.100 │ 0 │
│ 2023-03-24 00:24:03.200 │ 0 │
│ 2023-03-24 00:24:03.300 │ 0 │
│ 2023-03-24 00:24:03.400 │ 0 │
│ 2023-03-24 00:24:03.500 │ 0 │
│ 2023-03-24 00:24:03.600 │ 1 │
│ 2023-03-24 00:24:03.700 │ 0 │
│ 2023-03-24 00:24:03.800 │ 1 │
│ 2023-03-24 00:24:03.900 │ 1 │
│ 2023-03-24 00:24:04.000 │ 0 │
│ 2023-03-24 00:24:04.100 │ 0 │
│ 2023-03-24 00:24:04.200 │ 0 │
│ 2023-03-24 00:24:04.300 │ 1 │
│ 2023-03-24 00:24:04.400 │ 1 │
│ 2023-03-24 00:24:04.500 │ 0 │
│ 2023-03-24 00:24:04.600 │ 0 │
│ 2023-03-24 00:24:04.700 │ 0 │
│ 2023-03-24 00:24:04.800 │ 1 │
└─────────────────────────┴───────┘
从结果中我们可以看到,从 00:24:03.000 到 00:24:03.500 的所有桶现在都出现了。
WITH FILL...TO
我们仍然缺少时间范围末尾的一些桶,我们可以通过提供 TO 值来填充它们。TO 不包含在内,所以我们将向结束时间添加一小部分,以确保它被包含在内
SELECT
toStartOfInterval(timestamp, toIntervalMillisecond(100)) AS bucket,
count() AS count
FROM MidJourney.images
WHERE (timestamp >= {start:String}) AND (timestamp <= {end:String})
GROUP BY ALL
ORDER BY bucket ASC
WITH FILL
FROM toDateTime64({start:String}, 3)
TO toDateTime64({end:String}, 3) + INTERVAL 1 millisecond
STEP toIntervalMillisecond(100);
┌──────────────────bucket─┬─count─┐
│ 2023-03-24 00:24:03.000 │ 0 │
│ 2023-03-24 00:24:03.100 │ 0 │
│ 2023-03-24 00:24:03.200 │ 0 │
│ 2023-03-24 00:24:03.300 │ 0 │
│ 2023-03-24 00:24:03.400 │ 0 │
│ 2023-03-24 00:24:03.500 │ 0 │
│ 2023-03-24 00:24:03.600 │ 1 │
│ 2023-03-24 00:24:03.700 │ 0 │
│ 2023-03-24 00:24:03.800 │ 1 │
│ 2023-03-24 00:24:03.900 │ 1 │
│ 2023-03-24 00:24:04.000 │ 0 │
│ 2023-03-24 00:24:04.100 │ 0 │
│ 2023-03-24 00:24:04.200 │ 0 │
│ 2023-03-24 00:24:04.300 │ 1 │
│ 2023-03-24 00:24:04.400 │ 1 │
│ 2023-03-24 00:24:04.500 │ 0 │
│ 2023-03-24 00:24:04.600 │ 0 │
│ 2023-03-24 00:24:04.700 │ 0 │
│ 2023-03-24 00:24:04.800 │ 1 │
│ 2023-03-24 00:24:04.900 │ 0 │
│ 2023-03-24 00:24:05.000 │ 0 │
└─────────────────────────┴───────┘
所有空缺都已填充,我们现在有从 00:24:03.000 到 00:24:05.000 的每个 100 毫秒的条目。
累积计数
假设我们现在想保留桶中创建的图像数量的累积计数。我们可以通过添加一个 cumulative 列来做到这一点,如下所示
SELECT
toStartOfInterval(timestamp, toIntervalMillisecond(100)) AS bucket,
count() AS count,
sum(count) OVER (ORDER BY bucket) AS cumulative
FROM MidJourney.images
WHERE (timestamp >= {start:String}) AND (timestamp <= {end:String})
GROUP BY ALL
ORDER BY bucket ASC
WITH FILL
FROM toDateTime64({start:String}, 3)
TO toDateTime64({end:String}, 3) + INTERVAL 1 millisecond
STEP toIntervalMillisecond(100);
┌──────────────────bucket─┬─count─┬─cumulative─┐
│ 2023-03-24 00:24:03.000 │ 0 │ 0 │
│ 2023-03-24 00:24:03.100 │ 0 │ 0 │
│ 2023-03-24 00:24:03.200 │ 0 │ 0 │
│ 2023-03-24 00:24:03.300 │ 0 │ 0 │
│ 2023-03-24 00:24:03.400 │ 0 │ 0 │
│ 2023-03-24 00:24:03.500 │ 0 │ 0 │
│ 2023-03-24 00:24:03.600 │ 1 │ 1 │
│ 2023-03-24 00:24:03.700 │ 0 │ 0 │
│ 2023-03-24 00:24:03.800 │ 1 │ 2 │
│ 2023-03-24 00:24:03.900 │ 1 │ 3 │
│ 2023-03-24 00:24:04.000 │ 0 │ 0 │
│ 2023-03-24 00:24:04.100 │ 0 │ 0 │
│ 2023-03-24 00:24:04.200 │ 0 │ 0 │
│ 2023-03-24 00:24:04.300 │ 1 │ 4 │
│ 2023-03-24 00:24:04.400 │ 1 │ 5 │
│ 2023-03-24 00:24:04.500 │ 0 │ 0 │
│ 2023-03-24 00:24:04.600 │ 0 │ 0 │
│ 2023-03-24 00:24:04.700 │ 0 │ 0 │
│ 2023-03-24 00:24:04.800 │ 1 │ 6 │
│ 2023-03-24 00:24:04.900 │ 0 │ 0 │
│ 2023-03-24 00:24:05.000 │ 0 │ 0 │
└─────────────────────────┴───────┴────────────┘
cumulative 列中的值并没有像我们希望的那样工作。
WITH FILL...INTERPOLATE
任何在 count 列中具有 0 的行在 cumulative 列中也具有 0,而我们希望它使用 cumulative 列中的前一个值。我们可以通过使用 INTERPOLATE 子句来做到这一点,如下所示
SELECT
toStartOfInterval(timestamp, toIntervalMillisecond(100)) AS bucket,
count() AS count,
sum(count) OVER (ORDER BY bucket) AS cumulative
FROM MidJourney.images
WHERE (timestamp >= {start:String}) AND (timestamp <= {end:String})
GROUP BY ALL
ORDER BY bucket ASC
WITH FILL
FROM toDateTime64({start:String}, 3)
TO toDateTime64({end:String}, 3) + INTERVAL 100 millisecond
STEP toIntervalMillisecond(100)
INTERPOLATE (cumulative);
┌──────────────────bucket─┬─count─┬─cumulative─┐
│ 2023-03-24 00:24:03.000 │ 0 │ 0 │
│ 2023-03-24 00:24:03.100 │ 0 │ 0 │
│ 2023-03-24 00:24:03.200 │ 0 │ 0 │
│ 2023-03-24 00:24:03.300 │ 0 │ 0 │
│ 2023-03-24 00:24:03.400 │ 0 │ 0 │
│ 2023-03-24 00:24:03.500 │ 0 │ 0 │
│ 2023-03-24 00:24:03.600 │ 1 │ 1 │
│ 2023-03-24 00:24:03.700 │ 0 │ 1 │
│ 2023-03-24 00:24:03.800 │ 1 │ 2 │
│ 2023-03-24 00:24:03.900 │ 1 │ 3 │
│ 2023-03-24 00:24:04.000 │ 0 │ 3 │
│ 2023-03-24 00:24:04.100 │ 0 │ 3 │
│ 2023-03-24 00:24:04.200 │ 0 │ 3 │
│ 2023-03-24 00:24:04.300 │ 1 │ 4 │
│ 2023-03-24 00:24:04.400 │ 1 │ 5 │
│ 2023-03-24 00:24:04.500 │ 0 │ 5 │
│ 2023-03-24 00:24:04.600 │ 0 │ 5 │
│ 2023-03-24 00:24:04.700 │ 0 │ 5 │
│ 2023-03-24 00:24:04.800 │ 1 │ 6 │
│ 2023-03-24 00:24:04.900 │ 0 │ 6 │
│ 2023-03-24 00:24:05.000 │ 0 │ 6 │
└─────────────────────────┴───────┴────────────┘
看起来好多了。现在,为了完成它,让我们使用 bar 函数添加一个条形图,不要忘记将我们新列添加到 INTERPOLATE 子句中。
SELECT
toStartOfInterval(timestamp, toIntervalMillisecond(100)) AS bucket,
count() AS count,
sum(count) OVER (ORDER BY bucket) AS cumulative,
bar(cumulative, 0, 10, 10) AS barChart
FROM MidJourney.images
WHERE (timestamp >= {start:String}) AND (timestamp <= {end:String})
GROUP BY ALL
ORDER BY bucket ASC
WITH FILL
FROM toDateTime64({start:String}, 3)
TO toDateTime64({end:String}, 3) + INTERVAL 100 millisecond
STEP toIntervalMillisecond(100)
INTERPOLATE (cumulative, barChart);
┌──────────────────bucket─┬─count─┬─cumulative─┬─barChart─┐
│ 2023-03-24 00:24:03.000 │ 0 │ 0 │ │
│ 2023-03-24 00:24:03.100 │ 0 │ 0 │ │
│ 2023-03-24 00:24:03.200 │ 0 │ 0 │ │
│ 2023-03-24 00:24:03.300 │ 0 │ 0 │ │
│ 2023-03-24 00:24:03.400 │ 0 │ 0 │ │
│ 2023-03-24 00:24:03.500 │ 0 │ 0 │ │
│ 2023-03-24 00:24:03.600 │ 1 │ 1 │ █ │
│ 2023-03-24 00:24:03.700 │ 0 │ 1 │ █ │
│ 2023-03-24 00:24:03.800 │ 1 │ 2 │ ██ │
│ 2023-03-24 00:24:03.900 │ 1 │ 3 │ ███ │
│ 2023-03-24 00:24:04.000 │ 0 │ 3 │ ███ │
│ 2023-03-24 00:24:04.100 │ 0 │ 3 │ ███ │
│ 2023-03-24 00:24:04.200 │ 0 │ 3 │ ███ │
│ 2023-03-24 00:24:04.300 │ 1 │ 4 │ ████ │
│ 2023-03-24 00:24:04.400 │ 1 │ 5 │ █████ │
│ 2023-03-24 00:24:04.500 │ 0 │ 5 │ █████ │
│ 2023-03-24 00:24:04.600 │ 0 │ 5 │ █████ │
│ 2023-03-24 00:24:04.700 │ 0 │ 5 │ █████ │
│ 2023-03-24 00:24:04.800 │ 1 │ 6 │ ██████ │
│ 2023-03-24 00:24:04.900 │ 0 │ 6 │ ██████ │
│ 2023-03-24 00:24:05.000 │ 0 │ 6 │ ██████ │
└─────────────────────────┴───────┴────────────┴──────────┘