提取时间戳间隔最小的记录的优化函数

Posted

技术标签:

【中文标题】提取时间戳间隔最小的记录的优化函数【英文标题】:Optimising function which extracts records with a minimum gap in timestamps 【发布时间】:2019-05-16 16:15:43 【问题描述】:

我在 Postgres 9.4.5 中有一个大的时间戳表:

CREATE TABLE vessel_position (
  posid serial NOT NULL,
  mmsi integer NOT NULL,
  "timestamp" timestamp with time zone,
  the_geom geometry(PointZ,4326),
  CONSTRAINT "PK_posid_mmsi" PRIMARY KEY (posid, mmsi)
);

附加索引:

CREATE INDEX vessel_position_timestamp_idx ON vessel_position ("timestamp");

我想提取时间戳在前一行之后至少 x 分钟的每一行。我使用LAG() 尝试了一些不同的SELECT 语句,这些语句都有效,但没有给我我需要的确切结果。下面的函数给了我我需要的东西,但我觉得它可以更快:

CREATE OR REPLACE FUNCTION _getVesslTrackWithInterval(mmsi integer, startTime character varying (25) ,endTime character varying (25), interval_min integer)
RETURNS SETOF vessel_position AS
$func$
DECLARE
    count integer DEFAULT 0;
    posids varchar DEFAULT '';
    tbl CURSOR FOR
    SELECT
      posID
      ,EXTRACT(EPOCH FROM (timestamp -  lag(timestamp) OVER (ORDER BY posid asc)))::int as diff
    FROM vessel_position vp WHERE vp.mmsi = $1  AND vp.timestamp BETWEEN $2::timestamp AND $3::timestamp;
BEGIN
FOR row IN tbl
LOOP
    count := coalesce(row.diff,0) + count;
    IF count >= $4*60 OR count = 0 THEN
            posids:= posids || row.posid || ',';
            count:= 0;
     END IF;
END LOOP;
RETURN QUERY EXECUTE 'SELECT * from vessel_position where posid in (' || TRIM(TRAILING ',' FROM posids) || ')';
END
$func$ LANGUAGE plpgsql;

我不禁想到将所有posids 作为一个字符串,然后在最后再次选择它们会减慢速度。 在IF 语句中,我已经可以访问我想要保留的每一行,因此可以将它们存储在临时表中,然后在循环结束时返回临时表。

可以优化此功能 - 特别是提高性能吗?

【问题讨论】:

About the outdated Postgres version 9.4.5: We always recommend that all users run the latest available minor release for whatever major version is in use. 【参考方案1】:

查询

你的函数有各种昂贵的、不必要的开销。单个查询应该快很多倍,同样的做法:

CREATE OR REPLACE FUNCTION _get_vessel_track_with_interval
 (mmsi int, starttime timestamptz, endtime timestamptz, min_interval interval)
  RETURNS SETOF vessel_position AS
$func$
BEGIN
   SELECT (vp).*  -- parentheses required for decomposing row type
   FROM  (
      SELECT vp   -- whole row (!)
           , timestamp - lag(timestamp) OVER (ORDER BY posid) AS diff
      FROM   vessel_position vp
      WHERE  vp.mmsi = $1
      AND    vp.timestamp >= $2     -- typically you'd include the lower bound
      AND    vp.timestamp <  $3;    -- ... and exlude the upper
      ORDER  BY posid
      ) sub
   WHERE  diff >= $4;
END
$func$  LANGUAGE plpgsql STABLE;

也可以只是一个 SQL 函数或没有任何包装器的裸 SELECT(可能作为准备好的语句?Example.)

注意starttimeendtime 是如何作为timestamp 传递的。 (作为text 传递并转换是没有意义的。)最小间隔min_interval 是实际的interval。通过您选择的任何间隔。

索引

如果mmsi 上的谓词以任何方式具有选择性,那么您当前拥有的两个索引(PK ON (posid, mmsi)(timestamp) 上的idx)不是很有用。如果您将 PK 的列顺序反转为 (mmsi, posid),它对于手头的查询变得更加有用。见:

Is a composite index also good for queries on the first field?

对此的最佳索引通常位于vessel_position(mmsi, timestamp)。相关:

Multicolumn index and performance PostgreSQL performance with (col = value or col is NULL) Query does not hit the index - are these the proper columns to index?

旁白:避免将keywords 作为标识符。那是自找麻烦。另外,实际上包含timestamptz 的列timestamp 具有误导性。

【讨论】:

@wildplasser: nope :) 我在子查询中选择整行,并在外部SELECT 中将其分解以匹配声明为SETOF vessel_position 的返回类型,而没有拼出所有列。 (要去掉添加到子查询行的diff 列。) 我明白了。 (有点)sub 只是语法糖。 感谢您的回答,我很感激它确实回答了我最初提出的问题(很高兴接受它作为回答),但我意识到我错过了一个小而重要的细节(抱歉!)我现在在上面的编辑中添加了它。我已经实现了您的一些更改,即传递时间戳而不是 char,并以不同的方式格式化 WHERE,因此希望能够加快速度。 @BStone:我回滚了您的编辑更改问题(但保留了有用的补充)。您可以在edit history 中找到所有内容。我建议你为你改变的目标开始一个新的问题。 (我可能有一些想法。)您可以随时链接到这个以获得上下文 - 并在此处发表评论以链接回来并引起我的注意。 在您公开表定义后,还要注意timestamp -> timestamptz 的变化。否则当前时区设置可能会改变结果。理解含义很重要:***.com/a/9576170/939860

以上是关于提取时间戳间隔最小的记录的优化函数的主要内容,如果未能解决你的问题,请参考以下文章

查找 Unix 时间戳之间的最新间隔

Spark Sql:从时间戳按小时间隔分区

vb 时间戳转换成时间

提取时间戳在特定范围内的 Python Pandas 记录[关闭]

一个时间戳间隔8小时 时区的问题

性能优化之函数节流