如何获取每个设备的第一个和最后一个元素?
Posted
技术标签:
【中文标题】如何获取每个设备的第一个和最后一个元素?【英文标题】:How to get first and last element per device? 【发布时间】:2018-12-29 11:45:39 【问题描述】:我正在尝试找出在给定时间间隔内获取第一个元素和最后一个元素的最有效方法的答案。我有一张表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)
但是当设备数量增加时,响应时间需要很长时间。你有什么建议吗?如何更快地找到每个设备的第一个和最后一个元素?
【问题讨论】:
您的 Postgres 版本和表定义(CREATE TABLE
显示数据类型和约束的语句)对于任何涉及 SQL 的问题都有指导意义。特别是对于性能问题。以及您可能拥有的任何其他索引。以及是否可以更改表和索引。另外:id
或 created_at
的“第一”和“最后”?您是否希望结果中不包含任何区间数据的设备?
您还提到了result for each device
,但您的查询尝试是针对一小部分设备:device_id in (1000022, 1000023, 1000024)
。 可以大有作为。
感谢 Erwin,'created_at' 的第一个和最后一个含义。我只需要来自设备的 interval_data 和 device_id 的 value 和 created_at 。我写了device_id in (1000022, 1000023, 1000024)
部分作为示例,可以是两个设备,有时可以是八个设备。但是您的解决方案具有横向和限制 1 逻辑,效果非常好。
【参考方案1】:
您可以使用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'
Example at SQL Fiddle.
【讨论】:
感谢安多玛的回答。它对少数设备很有用,但如果设备数量增加,则需要一些时间。【参考方案2】:最有效的查询取决于您的设置细节。您可以在现有表 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)
上有一个索引。如果您只需要结果中的一组有限的列,则可能需要将更多列附加到该索引以获得 index-only 扫描。
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 约束对于此解决方案是可选的。
替代方案的详细解释和讨论:
Select first row in each GROUP BY group? Optimize GROUP BY query to retrieve latest record per user PostgreSQL: running count of rows for a query 'by minute'一小组给定设备 ID 的替代方案
如果您对使用自定义窗口框架的窗口函数感到满意,则此替代方法不需要额外的表 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);
与上面的第一个查询相同:
传递的不存在的设备 ID 没有结果。但与上面的第一个查询不同:
确实存在但没有任何间隔数据的已传递设备 ID 没有结果。关于窗框:
PostgreSQL query with max and min date plus associated id per row How to use a ring data structure in window functionsdb小提琴here
【讨论】:
感谢 Erwin 的详细解答。这是非常有帮助的。我不知道 LATERAL 太有用了。 @FurkanUyar:我添加了关于LATERAL
的详细信息的链接。以上是关于如何获取每个设备的第一个和最后一个元素?的主要内容,如果未能解决你的问题,请参考以下文章