【问题标题】:Postgresql - get closest datetime row relative to given datetime valuePostgresql - 获取相对于给定日期时间值最近的日期时间行
【发布时间】:2013-05-01 16:01:27
【问题描述】:

我有一个带有 unique 日期时间字段的 postgres 表。 我想使用/创建一个函数,该函数将日期时间值作为参数,并返回与传递的日期时间值最接近的日期时间相对(但不等于)的行 ID。第二个参数可以指定 beforeafter 传递的值。

理想情况下,本机datetime functions 的某种组合可以满足此要求。否则它必须是一个自定义函数。

问题:在行集合上查询相对日期时间的方法是什么?

【问题讨论】:

  • 泰德,你的问题是什么?如果您“仍在工作”,为什么要创建此帖子?
  • 如果有两条记录共享相同的日期时间值,并且都位于所需值的同一侧怎么办?
  • @MaksimKolesnikov 我添加了一个更清晰的问题。
  • @wildplasser 日期时间列是唯一的,因此排除了这种情况。但在更一般的情况下,我会返回第一个结果,或所有符合条件的结果。
  • 好吧:有三种可能性:1) 在子查询中使用NOT EXISTS 2) 与MIN()MAX() 相同。 3) 带有row_number() 和/或lead/lag() 的子查询。必须镜像前两个查询以允许上述/以下情况,第三个可能不允许。 (我不太确定第三个)

标签: postgresql datetime


【解决方案1】:
select id, passed_ts - ts_column difference
from t
where
    passed_ts > ts_column and positive_interval
    or
    passed_ts < ts_column and not positive_interval
order by abs(extract(epoch from passed_ts - ts_column))
limit 1

passed_ts 是时间戳参数,positive_interval 是布尔参数。如果为 true,则仅时间戳列低于传递的时间戳的行。如果为假,则相反。

【讨论】:

  • @ted.strauss 更新了新的修复程序
  • 此方法不会使用索引。它有点过时了。请参阅我的回答 newer method using knn
【解决方案2】:

简单地使用 -.

假设您有一个带有属性 Key、Attr 和 T(带有或不带有时区的时间戳)的表:

 you can search with

 select min(T - TimeValue) from Table where (T - TimeValue) > 0;

这将为您提供主要区别。您可以将此值与同一个表的连接结合起来以获得您感兴趣的元组:

 select * from (select *, T - TimeValue as diff from Table) as T1 NATURAL JOIN
               ( select min(T - TimeValue) as diff from Table where (T - TimeValue) > 0) as T2;

应该这样做

--dmg

【讨论】:

    【解决方案3】:

    您希望选择语句的第一行以降序(或升序)顺序生成低于(或高于)给定日期时间的所有行。

    函数体的伪代码:

    SELECT id
    FROM table
    WHERE IF(@above, datecol < @param, datecol > @param)
    ORDER BY IF (@above. datecol ASC, datecol DESC)
    LIMIT 1
    

    但是,这是行不通的:不能设置排序方向。

    第二个想法是做两个查询,然后选择:

    SELECT *
    FROM (
        (
        SELECT 'below' AS dir, id
        FROM table
        WHERE datecol < @param
        ORDER BY datecol DESC
        LIMIT 1
        ) UNION (
        SELECT 'above' AS dir, id
        FROM table
        WHERE datecol > @param
        ORDER BY datecol ASC
        LIMIT 1)
     ) AS t
     WHERE dir = @dir
    

    使用日期时间列上的索引应该很快。

    【讨论】:

    • 我认为分别查找第一个较高和第一个较低记录的原则可能是在数据集非常大的情况下表现最好的,但我认为您需要另一个级别的查询来确定哪个两者中最接近的。想一想,在公共表表达式中使用第一个查询,然后您可以在第二个查询中过滤,只返回比第一个找到的行更接近日期的行,这可能更有效。
    • @DavidAldridge 感谢您的建议:由于第二个参数,我不理解 OP 问题匹配绝对最接近的行(尽管我可能应该有)。我不知道 cte 是如何处理的,所以我决定不用。如果它们真的是在每个使用站点复制的某种宏,那么在那里使用它们是有意义的(并且考虑一下,如果可以将 where 子句应用于使用站点的 cte,那么它可能正在工作正确的方式)。
    【解决方案4】:
    -- test rig
    DROP SCHEMA tmp CASCADE;
    CREATE SCHEMA tmp ;
    SET search_path=tmp;
    
    CREATE TABLE lutser
            ( dt timestamp NOT NULL PRIMARY KEY
            );
    -- populate it
    INSERT INTO lutser(dt)
    SELECT gs
    FROM generate_series('2013-04-30', '2013-05-01', '1 min'::interval) gs
            ;
    DELETE FROM lutser WHERE random() < 0.9;
    

    --

    -- The query:
    WITH xyz AS (
            SELECT dt AS hh
            , LAG (dt) OVER (ORDER by dt ) AS ll
            FROM lutser
            )
    SELECT  *
    FROM xyz bb
    WHERE '2013-04-30 12:00' BETWEEN bb.ll AND bb.hh
            ;
    

    结果:

    NOTICE:  drop cascades to table tmp.lutser
    DROP SCHEMA
    CREATE SCHEMA
    SET
    NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "lutser_pkey" for table "lutser"
    CREATE TABLE
    INSERT 0 1441
    DELETE 1288
             hh          |         ll          
    ---------------------+---------------------
     2013-04-30 12:02:00 | 2013-04-30 11:50:00
    (1 row)
    

    将其包装成一个函数留给读者作为练习

    更新:这是第二个使用夹层不存在的技巧 (TM):

    SELECT lo.dt AS ll
    FROM lutser lo
    JOIN lutser hi ON hi.dt > lo.dt
            AND NOT EXISTS (
            SELECT * FROM lutser nx
            WHERE nx.dt < hi.dt
            AND nx.dt > lo.dt
            )
    WHERE '2013-04-30 12:00' BETWEEN lo.dt AND hi.dt
            ;
    

    【讨论】:

      【解决方案5】:

      您必须使用 where 条件将表连接到自身,以查找基表行的日期时间和连接表行的日期时间之间的最小非零(负或正)间隔。在该日期时间列上有一个索引会很好。

      附:您还可以查找前一个的 max() 或后续的 min()。

      【讨论】:

        【解决方案6】:

        尝试类似:

        SELECT * 
        FROM your_table
        WHERE (dt_time > argument_time and search_above = 'true')
           OR (dt_time < argument_time and search_above = 'false')
        ORDER BY CASE WHEN search_above = 'true' 
                      THEN dt_time - argument_time
                      ELSE argument_time - dt_time
                 END
        LIMIT 1;
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-01-13
          • 1970-01-01
          • 2014-05-22
          • 2020-10-02
          • 2015-09-10
          • 2021-06-18
          相关资源
          最近更新 更多