我正在尝试寻找在给定时间间隔内获取第一个元素和最后一个元素的最有效方法的答案。我有一个与interval_data
表有关系的表device
(包括类似物联网数据)。我想获得每个设备的第一个和最后一个元素的结果。
间隔数据表:
id device_id created_at value
15269665 1000206 2018-07-21 00:10:00 5099.550000
15270533 1000206 2018-07-21 00:20:00 5099.610000
15271400 1000206 2018-07-21 00:30:00 5099.760000
15272269 1000206 2018-07-21 00:40:00 5099.850000
15273132 1000206 2018-07-21 00:50:00 5099.910000
15274040 1000206 2018-07-21 01:00:00 5099.970000
15274909 1000206 2018-07-21 01:10:00 5100.030000
15275761 1000206 2018-07-21 01:20:00 5100.110000
15276629 1000206 2018-07-21 01:30:00 5100.160000
15277527 1000206 2018-07-21 01:40:00 5100.340000
15278351 1000206 2018-07-21 01:50:00 5100.400000
15279219 1000206 2018-07-21 02:00:00 5100.450000
15280085 1000206 2018-07-21 02:10:00 5100.530000
15280954 1000206 2018-07-21 02:20:00 5100.590000
15281858 1000206 2018-07-21 02:30:00 5100.640000
15282724 1000206 2018-07-21 02:40:00 5100.750000
15283627 1000206 2018-07-21 02:50:00 5100.870000
15284495 1000206 2018-07-21 03:00:00 5100.930000
... ... ... ...
我尝试了一些查询,例如:
select created_at, value from interval_data i inner join
(select min(created_at) minin, max(created_at) maxin, d.device_id from device
d
inner join interval_data i on i.device_id = d.device_id
where d.device_id in (1000022, 1000023, 1000024)
and i.created_at between '2018-01-01 00:00:00' and '2019-01-01 00:00:00'
group by d.device_id) s
on s.device_id = i.device_id and (s.minin = i.created_at or s.maxin =
i.created_at)
但是,当设备数量增加时,响应时间将花费很长时间。你有什么建议吗?如何更快地找到每个设备的第一个和最后一个元素?
答案 0 :(得分:0)
您可以使用row_number
向具有相同device_id
的每一行分配一个递增的数字。如果执行两次,一次升序,一次降序,则可以获取每个组的第一行和最后一行:
select device_id
, created_at
, value
from (
select row_number() over (partition by device_id order by created_at) rn1
, row_number() over (partition by device_id order by created_at desc) rn2
, *
from interval_data
) i
where device_id in (1, 3, 4)
and (rn1 = 1 or rn2 = 1) -- First or last row per device
and created_at between '2018-01-01 00:00:00' and '2019-01-01 00:00:00'
答案 1 :(得分:0)
最有效的查询取决于设置的详细信息。您可以建立在现有表device
上,并提及许多设计并显示每个设备的大量间隔数据。因此,通常,具有两个 LATERAL
子查询的查询应该最快:
SELECT * -- or just the columns you need
FROM device d
LEFT JOIN LATERAL (
SELECT id AS first_intv_id, created_at AS first_created_at, value AS first_value
FROM interval_data
WHERE device_id = d.id
ORDER BY created_at
LIMIT 1
) f ON true
LEFT JOIN LATERAL (
SELECT id AS last_intv_id, created_at AS last_created_at, value AS last_value
FROM interval_data
WHERE device_id = d.id
ORDER BY created_at DESC -- NULLS LAST if column isn't NOT NULL
LIMIT 1
) l ON true;
Postgres只需在大表interval_data
上进行快速索引扫描就可以将其转换为查询计划。
关于LATERAL
:
请确保在interval_data(device_id, created_at)
上有一个索引。如果结果中只需要一组有限的列,则可能需要向此索引追加更多列,以获取仅索引的扫描结果。
LEFT JOIN ... ON true
使结果中没有间隔数据的设备保持不变。
要限制给定的一组设备ID,请附加到查询:
...
WHERE d.id IN (1000022, 1000023, 1000024);
在device(id)
上有一个索引-仍然是典型的情况。
假设当前的Postgres版本和设置是这样的:
CREATE TABLE device (
id serial PRIMARY KEY
, device text NOT NULL
);
CREATE TABLE interval_data (
id serial PRIMARY KEY
, device_id int NOT NULL
, created_at timestamp NOT NULL
, value numeric NOT NULL
, CONSTRAINT device_fkey FOREIGN KEY (device_id) REFERENCES device (id)
);
如果未定义某些涉及的列NOT NULL
,则可能需要调整详细信息。
对于此解决方案,FK约束是可选的。
有关替代方案的详细说明和讨论:
如果您习惯使用自定义窗口框架的窗口功能,则该替代方法不需要额外的表device
,并且对于一小组ID可能更快:
SELECT DISTINCT ON (device_id)
device_id
, first_value(created_at) OVER w AS first_created_at
, first_value(value) OVER w AS first_value
, last_value (created_at) OVER w AS last_created_at
, last_value (value) OVER w AS last_value
FROM interval_data
WHERE device_id IN (1000022, 1000023, 1000024)
WINDOW w AS (PARTITION BY device_id ORDER BY created_at
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING);
与上面的第一个查询相同:
但是与上面的第一个查询不同 :
关于窗框:
db <>提琴here