[英]PostgreSQL - column value changed - select query optimization
假设我们有一张桌子:
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窗口函数可以提供更有效的解决方案吗?
这就是我用分析方法做的事情:
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
不匹配val
前一行(prev_val)的。 COALESCE
处理第一行的特殊情况,它没有以前的值。
分析函数起初可能看起来有点奇怪,但是对分析函数的搜索会发现很多例子都在讨论它们的工作原理。 例如: http : //www.cs.utexas.edu/~cannata/dbms/Analytic%20Functions%20in%20Oracle%208i%20and%209i.htm请记住,这是一个后处理步骤。 除非您对其进行子查询,否则您将无法对分析函数的值执行过滤等操作。
您可以直接从窗口函数lag()
提供默认值,而不是调用COALESCE
。 这种情况下的一个小细节,因为所有列都定义为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;
立即计算比较结果,因为前一个值本身并不重要,只有可能的变化。 更短,可能会更快一点。
如果您认为第一行被“更改”(与您的演示输出不同),您需要观察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演示了所有。
甚至可以不使用窗口功能。
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方法速度惊人,但需要隐式排序。
使用2 row_number()
计算 :这也可以使用通常的“孤岛和间隙”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 as(select *,row_number()over(order by id)作为rn1,row_number()over(按val顺序划分id)作为rn2从p)选择min(id)作为id,val来自cte1 group by 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
一个简单的内连接就可以做到。 SQL小提琴
select p2.id, p2.val
from
p p1
inner join
p p2 on p2.id = p1.id + 1
where p2.val != p1.val
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.