Nai*_*gun 7 sql postgresql window-functions gaps-and-islands
假设我们有一张桌子:
CREATE TABLE p
(
id serial NOT NULL,
val boolean NOT NULL,
PRIMARY KEY (id)
);
Run Code Online (Sandbox Code Playgroud)
填充了一些行:
insert into p (val)
values (true),(false),(false),(true),(true),(true),(false);
Run Code Online (Sandbox Code Playgroud)
ID VAL 1 1 2 0 3 0 4 1 5 1 6 1 7 0
我想确定何时更改了值.所以我的查询结果应该是:
ID VAL 2 0 4 1 7 0
我有一个连接和子查询的解决方案:
select min(id) id, val from
(
select p1.id, p1.val, max(p2.id) last_prev
from p p1
join p p2
on p2.id < p1.id and p2.val != p1.val
group by p1.id, p1.val
) tmp
group by val, last_prev
order by id;
Run Code Online (Sandbox Code Playgroud)
但它的效率非常低,对于有很多行的表来说效果会非常慢.
我相信使用PostgreSQL窗口函数可以提供更有效的解决方案吗?
这就是我如何通过分析来做到这一点:
SELECT id, val
FROM ( SELECT id, val
,LAG(val) OVER (ORDER BY id) AS prev_val
FROM p ) x
WHERE val <> COALESCE(prev_val, val)
ORDER BY id
Run Code Online (Sandbox Code Playgroud)
更新(一些解释):
分析功能充当后处理步骤。查询结果分为多个分组(partition by
),并且在分组的上下文中应用了分析功能。
在这种情况下,查询是从中进行的选择p
。应用的解析函数为LAG
。由于没有partition by
子句,因此只有一个分组:整个结果集。此分组按排序id
。 LAG
使用指定的顺序返回分组中上一行的值。结果是,每一行都有一个额外的列(别名为prev_val),该val
列是前一行的。那就是子查询。
然后,我们查找与上一行(prev_val)val
不匹配的val
行。该COALESCE
句柄处理不具有先前值的第一行的特殊情况。
解析函数起初可能看起来有些奇怪,但是对解析函数的搜索发现了很多示例,介绍了它们的工作方式。例如:http : //www.cs.utexas.edu/~cannata/dbms/Analytic%20Functions%20in%20Oracle%208i%20and%209i.htm请记住,这是一个后处理步骤。除非您对查询函数进行子查询,否则您将无法对其进行过滤等操作。
除了调用之外COALESCE
,您还可以lag()
直接从window函数提供默认值。由于定义了所有列,因此在这种情况下的详细信息很少NOT NULL
。但这对于区分“无上一行”和“上一行为NULL”可能是必不可少的。
SELECT id, val
FROM (
SELECT id, val, lag(val, 1, val) OVER (ORDER BY id) <> val AS changed
FROM p
) sub
WHERE changed
ORDER BY id;
Run Code Online (Sandbox Code Playgroud)
立即计算比较结果,因为先前的值本身并不重要,因此仅可能发生变化。更短,可能快一点。
如果您认为第一行已“更改”(与演示输出所建议的不同),则需要观察NULL
值-即使定义了列NOT NULL
。如果没有上一行,则基本lag()
返回NULL
:
SELECT id, val
FROM (
SELECT id, val, lag(val) OVER (ORDER BY id) IS DISTINCT FROM val AS changed
FROM p
) sub
WHERE changed
ORDER BY id;
Run Code Online (Sandbox Code Playgroud)
或lag()
再次使用的其他参数:
SELECT id, val
FROM (
SELECT id, val, lag(val, 1, NOT val) OVER (ORDER BY id) <> val AS changed
FROM p
) sub
WHERE changed
ORDER BY id;
Run Code Online (Sandbox Code Playgroud)
作为概念证明。:)性能无法跟上发布的替代方案。
WITH RECURSIVE cte AS (
SELECT id, val
FROM p
WHERE NOT EXISTS (
SELECT 1
FROM p p0
WHERE p0.id < p.id
)
UNION ALL
SELECT p.id, p.val
FROM cte
JOIN p ON p.id > cte.id
AND p.val <> cte.val
WHERE NOT EXISTS (
SELECT 1
FROM p p0
WHERE p0.id > cte.id
AND p0.val <> cte.val
AND p0.id < p.id
)
)
SELECT * FROM cte;
Run Code Online (Sandbox Code Playgroud)
通过@wildplasser的改进。
SQL Fiddle演示了所有内容。