跳过特定条件和正确的 Lead() 用法的 SQL 差距
Posted
技术标签:
【中文标题】跳过特定条件和正确的 Lead() 用法的 SQL 差距【英文标题】:Jump SQL gap over specific condition & proper lead() usage 【发布时间】:2012-09-18 16:06:18 【问题描述】:(PostgreSQL 8.4) 继续我的previous example,我希望进一步了解使用窗口函数处理间隙和孤岛。考虑下表和数据:
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
状态记录(同一设备连续两次)
显示来自当前ON
设备的适当OFF
状态
我能得到的最接近的是:
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
记录。返回记录3
、4
、5
和6
,但我不想要第五个,它是一个OFF
,它是在一个新的ON
之后出现的。所以我需要跳过这个差距,为当前活动的设备寻找下一个合适的OFF
。
-
10 关闭——在这种情况下是假的,但会弄乱 lag()
11 关闭——在这种情况下是假的,但会弄乱 lag()
11 打开 -- 好的,新序列,包含在 SELECT 中
10 次开启 -- 好的,新序列,包含在 SELECT 中
11 关闭 -- 消息迟到,需要忽略间隙
10 关闭 -- 好的,正确关闭到第 4 行,需要包含在 SELECT 中
一旦正确过滤,我想在它上面使用lead()
来获取下一行的id(想象一个时间戳),并过滤掉所有不是ON
状态的记录。我想这将需要三个嵌入式 SELECT 语句。这将使我清楚地了解设备处于活动状态的时间,直到另一个ON
或正确转向OFF
的条件。
【问题讨论】:
【参考方案1】:窗口函数查询
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
除了what we already had,我们还需要有效的OFF开关。
如果设备在 (last_status = 1
) 之前切换了 ON
并且之后的下一个 ON
操作出现在相关的 OFF
切换 (next_id_of_last_status > id
) 之后,则 OFF
切换有效。
我们必须提供最后一次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)
并且我们需要获取之前子查询中最后一个状态的下一个 id(子查询 t
)。
如果您已经理解了所有,那么在此查询之上添加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();
【讨论】:
这……太棒了。我应该把支票寄到哪里?说真的,还有一个额外的问题。为什么查询的 EXPLAIN ANALYZE 执行时间比实际执行时间长(3 倍)?作为参考,该表有大约 800 万条记录。 @denpanosekai:EXPLAIN ANALYZE 必须完成查询所做的所有事情以及一些额外的工作,因此它可能会更慢。不过,我认为这自 8.4 版以来有所改善。另外:当它最终完成时,结果如何?我希望该函数比查询快 3 到 4 倍 .. ? 其实都是13秒执行,但是函数返回381K记录,而Query返回327K记录。该死的,一定有一些额外的边缘条件我没有考虑到……我现在基本上是在分类垃圾。我认为这与同一时间戳上的重复 ON/OFF 记录有关(根据我的示例,实际上并未使用 ID 字段)。但是您的帮助非常宝贵,非常感谢您。 更重要的是,当测试干净的数据集(相同数量的记录,没有重复的时间戳)时,查询要快得多(能够在 20 毫秒内从 800 万条记录中提取 500 条记录,而 35 毫秒函数)据我所知,它们都没有使用索引,所以我不能说差异来自哪里。 @denpanosekai:这出乎意料。在 PostgreSQL 9.1.5 上的一个小数据集的快速测试中,该函数是迄今为止的赢家。它总是取决于许多因素。无论哪种方式都很酷,我同样爱我的两个孩子。 ;)以上是关于跳过特定条件和正确的 Lead() 用法的 SQL 差距的主要内容,如果未能解决你的问题,请参考以下文章