获取给定事件和时区的最近连续记录(连续天数)

问题描述 投票:0回答:1

这是一个艰难的问题,我一直在与之抗争。我有一张名为

action_events
的表,用于存储
event_name
timestamp

CREATE TABLE action_events (
  id SERIAL PRIMARY KEY,
  timestamp TIMESTAMP NOT NULL,
  event_name VARCHAR(255) NOT NULL
);

我想获取具有给定名称的事件的最新“连胜”。

“连续”是事件至少发生一次的连续天数。一个事件可能一天发生不止一次。大陷阱:连胜还应考虑到给定的时区。

使用这个小提琴: https://www.db-fiddle.com/f/4jyoMCicNSZpjMt4jFYoz5/7828

鉴于“MST”时区的“运动”查询,我预计连胜是:

连胜数 姓名 时区 开始日期 结束日期
13 “运动” “MST” 2023-02-18 09:00:00 2023-02-30 09:00:00

即使连胜在一个月前结束,它仍应显示为最近的连胜。

sql postgresql window-functions gaps-and-islands timestamp-with-timezone
1个回答
1
投票

Plain

timestamp
数据不知道时区。当我们不知道时间戳数据的时区时,给定的时区是没有意义的。
我假设您的时间戳应该代表 UTC。真的应该是
timestamptz
以避免歧义和额外的转换。

每个事件几行

纯 SQL。查询不会比这更快捷:

SELECT sum(ct)       AS streak_count
     , 'exercise'    AS event_name
     , 'MST'         AS timezone
     , min(min_ts)   AS start_date
     , max(max_ts)   AS end_date
FROM  (
   SELECT *, the_day - row_number() OVER (ORDER BY the_day)::int AS streak
   FROM  (
      SELECT (timestamp AT TIME ZONE 'UTC' AT TIME ZONE 'MST')::date AS the_day
           , count(*) AS ct
           , min(timestamp) AS min_ts
           , max(timestamp) AS max_ts           
      FROM   action_events
      WHERE  event_name = 'exercise'
      GROUP  BY 1
      ) sub1
   ) sub2
GROUP  BY streak
ORDER  BY end_date DESC
LIMIT  1;

小提琴

循序渐进

sub1

立即仅过滤感兴趣的

event_name
-示例中的
event_name = 'exercise'

timestamp AT TIME ZONE 'UTC'
产生
timestamptz
.
... AT TIME ZONE 'MST'
然后在 'MST' 处返回相应的
timestamp
。参见:

投射到

::date
the_day
),然后按此分组。
所以我们每天(最多)得到一行,并携带计数、最小值和最大值。

sub2

要识别条纹,只需从

integer
(键入
the_day
!)中减去行号(键入
date
!)。参见:

连续几天产生相同(否则无意义)的一天。

sub1
sub2
可以合并,但那会很笨重。)

外层
SELECT

对每条条纹进行聚合,取计数总和、最小值中的最小值和最大值中的最大值。 按最小值(或最大值,结果相同)降序排列并取第一行(

LIMIT 1
)。
瞧。

在结果中,

start_date
end_date
代表原始时间戳(UTC)。您可能希望在“MST”处显示时间戳。你没说。

没有定义最小条纹长度,所以它可能只是一行。

但是因为这会处理给定事件的所有行,所以它不能很好地扩展到很多行。

每个事件多行

从最新的行开始并循环直到遇到间隙会(快得多)快。虽然没有最小连胜长度要求,但我们不会出错。

演示 PL/pgSQL 函数:

CREATE OR REPLACE FUNCTION f_latest_streak(
   INOUT name        text
 , INOUT timezone    text
 , OUT   steak_count int
 , OUT   start_date  timestamp
 , OUT   end_date    timestamp)
  LANGUAGE plpgsql STRICT PARALLEL SAFE STABLE AS
$func$
DECLARE
   _day_start   timestamp;
   _start_date  timestamp;
   _streak_step int;
BEGIN
   -- get end & UTC time for start of latest day at given time zone
   SELECT max(a.timestamp)
        , date_trunc('day', max(a.timestamp) AT TIME ZONE 'UTC' AT TIME ZONE timezone)
          AT TIME ZONE 'UTC' AT TIME ZONE 'UTC'  -- sic!
   INTO   end_date, _day_start
   FROM   action_events a
   WHERE  event_name = name;
   
   IF NOT FOUND THEN  -- no rows at all
      RETURN;
   END IF;
   
   -- get count for first day
   SELECT count(*)::int, min(timestamp)
   INTO   steak_count, start_date
   FROM   action_events a
   WHERE  a.event_name = name  -- careful with naming conflicts!
   AND    a.timestamp >= _day_start;

   -- more days?
   LOOP
      SELECT count(*)::int, min(timestamp)
      INTO   _streak_step, _start_date
      FROM   action_events a
      WHERE  a.event_name = name  -- careful with naming conflicts!
      AND    a.timestamp >= _day_start - interval '1 day'
      AND    a.timestamp <  _day_start;
      
      IF _streak_step = 0 THEN  -- streak ends here
         RETURN;
      ELSE
         steak_count := steak_count + _streak_step;         
         start_date  := _start_date;
         _day_start  := _day_start - interval '1 day';
      END IF;
   END LOOP;
END
$func$;

电话:

SELECT * FROM f_latest_streak('exercise', 'MST');

(event_name, timestamp)
上的多列索引将使函数fast.

你应该熟悉 PL/pgSQL 才能玩这个。

© www.soinside.com 2019 - 2024. All rights reserved.