【问题标题】:Sum over a given time period给定时间段内的总和
【发布时间】:2022-12-01 17:29:18
【问题描述】:

以下代码给出了灯已打开的总持续时间。

CREATE TABLE switch_times (
  id SERIAL PRIMARY KEY, 
  is1 BOOLEAN, 
  id_dec INTEGER, 
  label TEXT, 
  ts TIMESTAMP WITH TIME ZONE default current_timestamp
);

CREATE VIEW makecount AS
SELECT *, row_number() OVER (PARTITION BY id_dec ORDER BY id) AS count
FROM switch_times;

select c1.label, SUM(c2.ts-c1.ts) AS sum
from
    (makecount AS c1
    inner join
    makecount AS c2 ON c2.count = c1.count + 1)
where c2.is1=FALSE AND c1.id_dec = c2.id_dec AND c2.is1 != c1.is1
GROUP BY c1.label;

工作演示链接https://dbfiddle.uk/ZR8pLEBk

关于如何更改代码以便给出给定特定时间段内的总和的任何建议,比如 25 日,在此期间所有三个灯都打开了 12 小时?问题一:当前代码给出总和,如下。问题 2:所有未结束的持续时间都被忽略,因为没有关闭时间。

label       sum
0x29 MH3    1 day 03:00:00
0x2B MH1    1 day 01:00:00
0x2C MH2    1 day 02:00:00

预期结果刚好超过给定日期,即

label       sum
0x29 MH3    12:00:00
0x2B MH1    12:00:00
0x2C MH2    12:00:00

【问题讨论】:

  • 你能提供你的预期输出吗?
  • 请最小化您的示例,将其也放入问题中并添加预期结果。
  • 按照建议修改。
  • 请解释列的含义。明确任务。您是只想要三个给定标签的结果,还是想要出现在表格中的所有标签的结果,或者想要在给定时间段内有任何条目的标签的结果? switch_times 表中大约有多少行?有多少不同的标签?是否有每个(相关)标签一行的表格,如“标签”?你的 Postgres 版本?所有列(除了id)都可以是NULL?你需要风景?定义“25日”接近一点了。 (日期取决于其时区。)

标签: sql postgresql gaps-and-islands postgresql-performance


【解决方案1】:

假设以下(应在问题中定义):

  • Postgres 15。
  • 表很大,性能很重要,我们可以加索引。
  • 所有的列实际上都是NOT NULL,你只是忘了这样声明列。
  • 每个“light”都有一个不同的id_dec 和一个不同的label。两者都在switch_times 中是多余的。 (正常化!)
  • 如果最近的早期条目有is1 IS TRUE,则灯“打开”。否则它被认为是“关闭”。
  • 行的顺序是由ts 建立的,而不是由查询中使用的id 建立的(通常是不正确的)。
  • 不必连续输入改变状态。
  • (id_dec, ts) 没有重复条目。 (有一个唯一的索引强制执行。)
  • 条目之间没有最小或最大时间间隔。
  • “The 25th”应该是指tstzrange '[2022-11-25 0:0+02, 2022-11-26 0:0+02)'(注意时区偏移量。)
  • 您想要在给定时间间隔内打开的所有标签的结果。
  • 有一张“标签”表,每个相关灯都有一个不同的条目。如果您没有,请创建它。

索引

至少有这些索引可以使一切变得更快:

CREATE INDEX ON switch_times (id_dec, ts DESC);
CREATE INDEX ON switch_times (ts);

创建表 labels 的可选步骤

CREATE TABLE labels AS
WITH RECURSIVE cte AS (
   (
   SELECT id_dec, label
   FROM   switch_times
   ORDER  BY 1
   LIMIT  1
   )

   UNION ALL
   (
   SELECT s.id_dec, s.label
   FROM   cte c
   JOIN   switch_times s ON s.id_dec > c.id_dec
   ORDER  BY 1
   LIMIT  1
   )
   )
TABLE cte;

ALTER TABLE labels
  ADD PRIMARY KEY (id_dec)
, ALTER COLUMN label SET NOT NULL
, ADD CONSTRAINT label_uni UNIQUE (label)  
;

为什么这样?看:

主要查询

WITH bounds(lo, hi) AS (
   SELECT timestamptz '2022-11-25 0:0+02'  -- enter time interval here *once*
        , timestamptz '2022-11-26 0:0+02'
   )
, snapshot AS (
   SELECT id_dec, label, is1, ts
   FROM   switch_times s, bounds b
   WHERE  s.ts >= b.lo
   AND    s.ts <  b.hi

   UNION ALL  -- must be separate   
   SELECT s.*
   FROM   labels l
   JOIN   LATERAL ( -- latest earlier entry 
      SELECT s.id_dec, s.label, s.is1, b.lo AS ts  -- cut off at lower bound
      FROM   switch_times s, bounds b
      WHERE  s.id_dec = l.id_dec
      AND    s.ts < b.lo
      ORDER  BY s.ts DESC
      LIMIT  1
      ) s ON s.is1  -- ... if it's "on"
   )
SELECT label, sum(z - a) AS duration
FROM  (
   SELECT label
        , lag(is1, 1, false) OVER w AS last_is1
        , lag(ts) OVER w AS a
        , ts AS z
   FROM   snapshot
   WINDOW w AS (PARTITION BY label ORDER BY ts ROWS UNBOUNDED PRECEDING)
   ) sub
WHERE  last_is1
GROUP  BY 1;

fiddle

CTEbounds是一个可选的便利功能,用于输入您的时间间隔的下限和上限一次.

CTEsnapshot收集所有感兴趣的行,其中包括

  1. 时间间隔内的所有行(UNION ALL 查询的第一段)
  2. 最新的较早的行,如果它是“on”(UNION ALL查询的第二段)

    我们需要聚集2.分别覆盖灯较早打开并且在给定时间间隔内没有条目的角落情况!但是我们可以立即将时间戳替换为下限。

    最后的查询获取子查询中每一行的前一个(is1, ts),如果没有前一行则默认为“关闭”。

    最后总结一下外层SELECT的区间。仅对开始时打开的内容求和(无论最终状态如何)。

    有关的:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-08-03
    • 1970-01-01
    • 1970-01-01
    • 2018-07-27
    • 2021-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多