假设我们有一张桌子:
CREATE TABLE p
(
id serial NOT NULL,
val boolean NOT NULL,
PRIMARY KEY (id)
);
填充了一些行:
insert into p (val)
values (true),(false),(false),(true),(true),(true),(false);
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;
但效率非常低,对于有很多行的表来说效果会非常慢 我相信使用PostgreSQL窗口函数可以提供更有效的解决方案吗?
答案 0 :(得分:4)
这就是我用分析方法做的事情:
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
更新(一些解释):
分析功能作为后处理步骤运行。查询结果分为分组(partition by
),分析函数应用于分组的上下文中。
在这种情况下,查询是p
的选择。正在应用的分析函数是LAG
。由于没有partition by
子句,因此只有一个分组:整个结果集。此分组按id
排序。 LAG
使用指定的顺序返回分组中上一行的值。结果是每行都有一个额外的列(别名prev_val),它是前一行的val
。那是子查询。
然后我们查找val
与前一行(prev_val)的val
不匹配的行。 COALESCE
处理第一行的特殊情况,该特殊情况没有先前的值。
分析函数起初可能看起来有点奇怪,但是对分析函数的搜索会发现很多例子都在讨论它们的工作原理。例如:http://www.cs.utexas.edu/~cannata/dbms/Analytic%20Functions%20in%20Oracle%208i%20and%209i.htm请记住,这是一个后处理步骤。除非您对其进行子查询,否则您将无法对分析函数的值执行过滤等。
答案 1 :(得分:4)
您可以直接从窗口函数lag()
提供默认值,而不是调用COALESCE
。这种情况下的一个小细节,因为所有列都已定义NOT NULL
。但这可能是必要的,以区分&#34;没有前一行&#34;来自&#34;前一行中的NULL&#34;。
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;
立即计算比较结果,因为前一个值本身并不重要,只有可能的变化。更短,可能会更快一点。
如果,您认为 第一行 是&#34;已更改&#34; (与您的演示输出建议不同),您需要观察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;
或再次使用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;
作为概念的证明。 :) 绩效不会跟上发布的替代方案。
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;
随着@wildplasser的改进。
SQL Fiddle展示所有。
答案 2 :(得分:2)
甚至可以在没有窗口功能的情况下完成。
SELECT * FROM p p0
WHERE EXISTS (
SELECT * FROM p ex
WHERE ex.id < p0.id
AND ex.val <> p0.val
AND NOT EXISTS (
SELECT * FROM p nx
WHERE nx.id < p0.id
AND nx.id > ex.id
)
);
更新:自加入非递归CTE(也可以是子查询而不是CTE)
WITH drag AS (
SELECT id
, rank() OVER (ORDER BY id) AS rnk
, val
FROM p
)
SELECT d1.*
FROM drag d1
JOIN drag d0 ON d0.rnk = d1.rnk -1
WHERE d1.val <> d0.val
;
这种非递归的CTE方法速度惊人,但需要隐式排序。
答案 3 :(得分:1)
使用2 row_number()
次计算:这也可能与通常的&#34;岛和间隙&#34; SQL技术(如果由于某种原因你不能使用lag()
窗口函数可能很有用:
with cte1 as (
select
*,
row_number() over(order by id) as rn1,
row_number() over(partition by val order by id) as rn2
from p
)
select *, rn1 - rn2 as g
from cte1
order by id
因此,此查询将为您提供所有岛屿
ID VAL RN1 RN2 G
1 1 1 1 0
2 0 2 1 1
3 0 3 2 1
4 1 4 2 2
5 1 5 3 2
6 1 6 4 2
7 0 7 3 4
你看,G
字段如何用于将这些岛屿组合在一起:
以cte1为( 选择 * row_number()over(按id排序)为rn1, row_number()over(由id by val order by partition)为rn2 来自p ) 选择 min(id)为id, VAL 来自cte1 val,rn1 - rn2分组 按1排序
所以你得到
ID VAL
1 1
2 0
4 1
7 0
现在唯一的事情就是你必须删除第一条记录,这可以通过获取min(...) over()
窗口函数来完成:
with cte1 as (
...
), cte2 as (
select
min(id) as id,
val,
min(min(id)) over() as mid
from cte1
group by val, rn1 - rn2
)
select id, val
from cte2
where id <> mid
结果:
ID VAL
2 0
4 1
7 0
答案 4 :(得分:0)
简单的内部联接可以做到这一点。 SQL Fiddle
select p2.id, p2.val
from
p p1
inner join
p p2 on p2.id = p1.id + 1
where p2.val != p1.val