PostgreSQL - 列值已更改 - 选择查询优化

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窗口函数可以提供更有效的解决方案吗?

SQL小提琴

Gle*_*enn 5

这就是我如何通过分析来做到这一点:

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子句,因此只有一个分组:整个结果集。此分组按排序idLAG使用指定的顺序返回分组中上一行的值。结果是,每一行都有一个额外的列(别名为prev_val),该val列是前一行的。那就是子查询。

然后,我们查找与上一行(prev_val)val不匹配的val行。该COALESCE句柄处理不具有先前值的第一行的特殊情况。

解析函数起初可能看起来有些奇怪,但是对解析函数的搜索发现了很多示例,介绍了它们的工作方式。例如:http : //www.cs.utexas.edu/~cannata/dbms/Analytic%20Functions%20in%20Oracle%208i%20and%209i.htm请记住,这是一个后处理步骤。除非您对查询函数进行子查询,否则您将无法对其进行过滤等操作。


Erw*_*ter 5

视窗功能

除了调用之外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)

递归CTE

作为概念证明。:)性能无法跟上发布的替代方案。

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演示了所有内容。