1

(PostgreSQL 8.4)继续我之前的示例,我希望进一步了解使用窗口函数处理间隙和孤岛。考虑下表和数据:

CREATE TABLE T1
(
  id SERIAL PRIMARY KEY,
  val INT,   -- some device
  status INT -- 0=OFF, 1=ON
);

INSERT INTO T1 (val, status) VALUES (10, 0);
INSERT INTO T1 (val, status) VALUES (11, 0);
INSERT INTO T1 (val, status) VALUES (11, 1);
INSERT INTO T1 (val, status) VALUES (10, 1);
INSERT INTO T1 (val, status) VALUES (11, 0);
INSERT INTO T1 (val, status) VALUES (10, 0);

如前所述,设备会打开和关闭,这一次我希望提取一个特定的序列:

  • 显示所有ON不重复的新状态记录(同一设备连续两次)
  • OFF从当前ON设备显示适当的状态

我能得到的最接近的是:

SELECT * FROM  (
  SELECT *
       ,lag(val, 1, 0) OVER (PARTITION BY status ORDER BY id) last_val
       ,lag(status, 1, -1) OVER (PARTITION BY val ORDER BY id) last_status
  FROM   t1
  ) x
WHERE  (last_val <> val OR last_status <> status)
AND    (status = 1 OR last_status <> -1)
ORDER BY id

这会过滤掉样本不包含的更多虚假数据,但本质上是关于取出后续重复项(无论状态如何)和OFF不匹配的顶级记录。Records 3, 4, 5and6被返回,但我不想要第五个,它OFF是在 new 之后出现的ON。所以我需要跳过这个差距并为OFF当前活动的设备寻找下一个合适的设备。

  1. 10 关闭——在这种情况下是假的,但会弄乱 lag()
  2. 11 关闭——在这种情况下是假的,但会弄乱 lag()
  3. 11 开启 -- 好的,新序列,包含在 SELECT 中
  4. 10 次打开 -- 好的,新序列,包含在 SELECT 中
  5. 11 关闭——消息来晚了,需要忽略间隙
  6. 10 次关闭 -- 好的,正确关闭到第 4 行,需要包含在 SELECT 中

一旦正确过滤,我想lead()在这一切之上使用来获取下一行的 id(想象一个时间戳)以及过滤掉所有不是ON状态的记录。我想这将需要三个嵌入式 SELECT 语句。这将使我清楚地了解设备处于活动状态的时间,直到另一个ON或适当转弯的条件OFF

4

1 回答 1

3

使用窗口函数查询

SELECT *
FROM  (
   SELECT *
         ,lag(val, 1, 0)    OVER (PARTITION BY status ORDER BY id) AS last_val
         ,lag(status, 1, 0) OVER w2 AS last_status
         ,lag(next_id)      OVER w2 AS next_id_of_last_status
   FROM  (
      SELECT *, lead(id) OVER (PARTITION BY status ORDER BY id) AS next_id
      FROM   t1
      ) AS t
   WINDOW w2 AS (PARTITION BY val ORDER BY id)
  ) x
WHERE (last_val <> val OR last_status <> status)
AND   (status = 1 
       OR last_status = 1
          AND ((next_id_of_last_status > id) OR next_id_of_last_status IS NULL)
      )
ORDER  BY id

除了我们已经拥有的,我们还需要有效的 OFF 开关。

如果设备在 ( )之前OFF切换并且之后的下一个操作在相关切换( ) 之后进行,则切换有效。ONlast_status = 1ONOFFnext_id_of_last_status > id

我们必须提供最后一次ON操作的特殊情况,所以我们检查NULL加法(OR next_id_of_last_status IS NULL)。

来自next_id_of_last_status我们从中获取的同一窗口last_status。因此我为显式窗口声明引入了额外的语法,所以我不必重复自己:

WINDOW w2 AS (PARTITION BY val ORDER BY id)

我们需要在之前的子查询(subquery t)中获取最后一个状态的下一个 id。

如果您已经理解了所有这些lead(),那么在此查询之上进行处理以到达最终目的地应该没有问题。:)

PL/pgSQL 函数

一旦变得如此复杂,就该切换到程序处理了。

这个相对简单的plpgsql 函数削弱了复杂窗口函数查询的性能,原因很简单,它只需扫描整个表一次。

CREATE OR REPLACE FUNCTION valid_t1 (OUT t t1)  -- row variable of table type
  RETURNS SETOF t1 LANGUAGE plpgsql AS
$func$
DECLARE
   _last_on int := -1;  -- init with impossible value
BEGIN

FOR t IN
   SELECT * FROM t1 ORDER BY id
LOOP
   IF t.status = 1 THEN
      IF _last_on <> t.val THEN
         RETURN NEXT;
         _last_on := t.val;
      END IF;
   ELSE
      IF _last_on = t.val THEN
         RETURN NEXT;
         _last_on := -1;
      END IF;
   END IF;
END LOOP;

END
$func$;

称呼:

SELECT * FROM valid_t1();
于 2012-09-19T20:57:19.360 回答