此页面包含来自 TimescaleDB 社区 关于如何解决常见问题的建议。使用这些代码示例作为指导来处理您自己的数据。
要遵循此页面中的示例,您需要一个
本节包含关于超表的实践。
想要从现有超表中删除重复项吗?一种方法是运行 PARTITION BY
查询以获取 ROW_NUMBER()
,然后获取 row_number>1
的行的 ctid
。然后删除这些行。但是,您需要检查 tableoid
和 ctid
。这是因为 ctid
不是唯一的,并且可能在不同的块中重复。以下代码示例花了 17 个小时处理一个包含 4000 万行的表
CREATE OR REPLACE FUNCTION deduplicate_chunks(ht_name TEXT, partition_columns TEXT, bot_id INT DEFAULT NULL)RETURNS TABLE(chunk_schema name,chunk_name name,deleted_count INT)AS$$DECLAREchunk RECORD;where_clause TEXT := '';deleted_count INT;BEGINIF bot_id IS NOT NULL THENwhere_clause := FORMAT('WHERE bot_id = %s', bot_id);END IF;FOR chunk INSELECT c.chunk_schema, c.chunk_nameFROM timescaledb_information.chunks cWHERE c.hypertable_name = ht_nameLOOPEXECUTE FORMAT('WITH cte AS (SELECT ctid,ROW_NUMBER() OVER (PARTITION BY %s ORDER BY %s ASC) AS row_num,*FROM %I.%I%s)DELETE FROM %I.%IWHERE ctid IN (SELECT ctidFROM cteWHERE row_num > 1)RETURNING 1;', partition_columns, partition_columns, chunk.chunk_schema, chunk.chunk_name, where_clause, chunk.chunk_schema,chunk.chunk_name)INTO deleted_count;RETURN QUERY SELECT chunk.chunk_schema, chunk.chunk_name, COALESCE(deleted_count, 0);END LOOP;END$$ LANGUAGE plpgsql;SELECT *FROM deduplicate_chunks('nudge_events', 'bot_id, session_id, nudge_id, time', 2540);
感谢 Mathias Ose 和 Christopher Piggott 提供的这个实践。
假设有一个查询将超表连接到另一个表,它们共享一个键
SELECT timestamp,FROM hypertable as hJOIN related_table as rtON rt.id = h.related_table_idWHERE h.timestamp BETWEEN '2024-10-10 00:00:00' AND '2024-10-17 00:00:00'
如果您在此查询上运行 EXPLAIN
,您会看到查询计划器在这两个表之间执行 NestedJoin
,这意味着多次查询超表。即使超表已建立索引,但如果它也很大,则查询将很慢。如何强制只查找一次?使用物化公共表表达式 (CTE)。
如果您使用 CTE 将查询拆分为两个部分,则可以物化超表查找并强制 PostgreSQL 仅执行一次。
WITH cached_query AS materialized (SELECT *FROM hypertableWHERE BETWEEN '2024-10-10 00:00:00' AND '2024-10-17 00:00:00')SELECT *FROM cached_query as cJOIN related_table as rtON rt.id = h.related_table_id
现在,如果您再次运行 EXPLAIN
,您会看到此查询仅执行一次查找。根据您的超表的大小,这可能会使一个耗时数小时的查询只需几秒钟即可完成。
感谢 Rowan Molony 提供的这个实践。
本节包含关于物联网问题的实践
窄表和中等宽度表是存储物联网数据的好方法。在 设计数据库模式:宽表与窄表 Postgres 表 中概述了很多原因。
窄表的关键优势之一是当您添加新传感器时,模式不必更改。另一个巨大的优势是每个传感器可以以不同的速率和时间进行采样。这有助于支持诸如滞后之类的功能,即除非值发生一定量的变化,否则新值很少写入。
使用窄表数据结构会带来一些挑战。在物联网世界中,一个令人担忧的问题是,许多数据分析方法(包括机器学习以及更传统的数据分析)都要求您的数据被重新采样并同步到共同的时间基准。幸运的是,TimescaleDB 为您提供了 hyperfunctions 和其他工具来帮助您处理这些数据。
窄表格式的一个例子是
ts | sensor_id | value |
---|---|---|
2024-10-31 11:17:30.000 | 1007 | 23.45 |
通常,您会将其与传感器表结合使用
sensor_id | sensor_name | units |
---|---|---|
1007 | temperature | degreesC |
1012 | heat_mode | on/off |
1013 | cooling_mode | on/off |
1041 | occupancy | 房间内人数 |
中等表保留了通用结构,但添加了各种类型的列,以便您可以使用同一个表来存储 float、int、bool 甚至 JSON (jsonb) 数据
ts | sensor_id | d | i | b | t | j |
---|---|---|---|---|---|---|
2024-10-31 11:17:30.000 | 1007 | 23.45 | null | null | null | null |
2024-10-31 11:17:47.000 | 1012 | null | null | TRUE | null | null |
2024-10-31 11:18:01.000 | 1041 | null | 4 | null | null | null |
要删除所有空条目,请使用可选约束,例如
CONSTRAINT at_least_one_not_nullCHECK ((d IS NOT NULL) OR (i IS NOT NULL) OR (b IS NOT NULL) OR (j IS NOT NULL) OR (t IS NOT NULL))
有几种方法可以获取每个传感器的最新值。以下示例使用 窄表格式示例 中定义的结构作为参考
SELECT DISTINCT ON
如果您有一个传感器列表,则获取每个传感器的最新值的简单方法是使用 SELECT DISTINCT ON
WITH latest_data AS (SELECT DISTINCT ON (sensor_id) ts, sensor_id, dFROM iot_dataWHERE d is not nullAND ts > CURRENT_TIMESTAMP - INTERVAL '1 week' -- importantORDER BY sensor_id, ts DESC)SELECTsensor_id, sensors.name, ts, dFROM latest_dataLEFT OUTER JOIN sensors ON latest_data.sensor_id = sensors.idWHERE latest_data.d is not nullORDER BY sensor_id, ts; -- Optional, for displaying results ordered by sensor_id
上面使用的公共表表达式 (CTE) 不是绝对必要的。然而,这是一种优雅的方式来连接到传感器列表以在输出中获取传感器名称。如果您不关心这一点,可以将其省略
SELECT DISTINCT ON (sensor_id) ts, sensor_id, dFROM iot_dataWHERE d is not nullAND ts > CURRENT_TIMESTAMP - INTERVAL '1 week' -- importantORDER BY sensor_id, ts DESC
在向下选择此数据时,务必小心。在前面的示例中,查询将回溯扫描的时间受到限制。但是,如果任何传感器长时间未报告,或者在最坏的情况下从未报告,则此查询将演变为全表扫描。
在具有 1000 多个传感器和 4100 万行的数据库中,不受约束的查询需要一个多小时。
SELECT DISTINCT ON
的替代方法是使用 JOIN LATERAL
。通过从传感器表中选择您的整个传感器列表,而不是使用 SELECT DISTINCT
拉出 ID,JOIN LATERAL
可以提供一些性能改进
SELECT sensor_list.id, latest_data.ts, latest_data.dFROM sensors sensor_list-- Add a WHERE clause here to downselect the sensor list, if you wishLEFT JOIN LATERAL (SELECT ts, dFROM iot_data raw_dataWHERE sensor_id = sensor_list.idORDER BY ts DESCLIMIT 1) latest_data ON trueWHERE latest_data.d is not null -- only pulling out float values ("d" column) in this exampleAND latest_data.ts > CURRENT_TIMESTAMP - interval '1 week' -- importantORDER BY sensor_list.id, latest_data.ts;
限制时间范围非常重要,尤其是在您有大量数据的情况下。最佳实践是将此类查询用于仪表板和快速状态检查。要查询更大的时间范围,请将前面的示例封装到不经常刷新的物化查询中,也许每天一次。
感谢 Christopher Piggott 提供的这个实践。
在此页面上发现问题?报告问题 或 在 GitHub 上编辑此页面。