PostgreSQL: WITH Queries (Common Table Expressions)
WITH 允许在 SELECT 语句中定义"表"的表达式,这个"表"的表达式称之为"公共表表达式(Common Table Expression)",简称 CTE,仅在该 SELECT 语句范围内有效。CTE 的作用和临时表类似,CTE 的使用和 VIEW(视图) 类似,区别在于 CTE 不需要提前定义,VIEW 需要提前定义。
SELETCT IN WITH
先来看一个例子:
WITH regional_sales AS (
SELECT region, SUM(amount) AS total_sales
FROM orders
GROUP BY region
), top_regions AS (
SELECT region
FROM regional_sales
WHERE total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales)
)
SELECT region,
product,
SUM(quantity) AS product_units,
SUM(amount) AS product_sales
FROM orders
WHERE region IN (SELECT region FROM top_regions)
GROUP BY region, product;
该例中,使用 WITH 定义了两个 CTE, 对应两张表:regional_sales 和 top_regions ,其结果集就是紧随其后的SELECT:
SELECT region, SUM(amount) AS total_sales FROM orders GROUP BY region; -- regional_sales 结果集
SELECT region FROM regional_sales WHERE total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales); -- top_regions 结果集
SQL含义就不多解释了,只是想展示 WITH 语法基本格式与用法。需要明确一点 "这是一个SQL语句" 。
再来看一个例子:
WITH RECURSIVE t(n) AS (
VALUES (1)
UNION ALL
SELECT n+1 FROM t WHERE n < 100
)
SELECT sum(n) FROM t;
这个例子是用 CTE 配合 RECURSIVE(递归),对 1 ~ 100 自然数求和。
举这个例子目的不在于递归,而是想说明定义CTE的时候,可以采用 "t(n) AS (...)" 形式,t 是表名,n 是自定义列名,可定义多列,如: t(n,m) , t(a,b,c) 。但需注意的是,定义的列数必须和 AS 后面的结果集保持一致,数据类型无须定义,由结果集匹配。
WITH RECURSIVE
WITH 配合 RECURSIVE 关键字,可以递归查询生成 CTE 结果集,请看下面的例子:
CREATE TABLE department (
ID INTEGER PRIMARY KEY, -- department ID
parent_department INTEGER REFERENCES department, -- upper department ID
NAME TEXT -- department name
); INSERT INTO department (ID, parent_department, "name")
VALUES
(0, NULL, 'ROOT'),
(1, 0, 'A'),
(2, 1, 'B'),
(3, 2, 'C'),
(4, 2, 'D'),
(5, 0, 'E'),
(6, 4, 'F'),
(7, 5, 'G'); -- department structure represented here is as follows:
--
-- ROOT-+->A-+->B-+->C
-- | |
-- | +->D-+->F
-- +->E-+->G
创建 department 表,表中数据是一对多的父子关系,呈现树结构。假如要获取A部门及其下属部门的信息,可以使用下面的查询语句:
WITH RECURSIVE subdepartment AS
(
-- non-recursive term
SELECT * FROM department WHERE name = 'A' UNION ALL -- recursive term
SELECT d.*
FROM
department AS d
JOIN
subdepartment AS sd
ON (d.parent_department = sd.id)
)
SELECT *
FROM subdepartment
ORDER BY name;
重点就是解释一下该语句的递归(RECURSIVE)流程,仅仅是逻辑理解该递归语句,具体底层执行原理不清楚:
首先假设两张临时表:工作表(WorkTable),结果表(ResultTable)。
第一步:执行非递归部分(non-recursive term),即:
-- non-recursive term
SELECT * FROM department WHERE name = 'A'
返回结果集同时放在 WITH 定义的 subdepartment 和 ResultTable 中。
第二步:执行递归部分(recursive term),即
-- recursive term
SELECT d.*
FROM
department AS d
JOIN
subdepartment AS sd
ON (d.parent_department = sd.id)
执行完后得到临时结果集,放入 WorkTable 中;
WorkTable 若不为空,用 WorkTable 结果集替换 subdepartment 结果集,同时把 WorkTable 结果集加入到 ResultTable 结果集中。然后清空 WorkTable,回到第二步继续执行。
WorkTable 若为空,跳出循环。
第三步:递归结束,跳出循环后,ResultTable 就保存着最终的结果集,用 ResultTable 结果集替换 subdepartment 结果集。
WITH RECURSIVE 死循环
WITH RECURSIVE t(n) AS (
SELECT 1
UNION ALL
SELECT n+1 FROM t
)
SELECT n FROM t LIMIT 100;
这个 WITH 中,递归是个死循环,在与 WITH 平级的 SELECT 上面加 "LIMIT 100" 的限制,可以避免死循环。PostgreSQL原理是:一旦递归出来的结果集数据,能够满足该 SELECT 的查询要求,就会终止递归,不再做无谓的查询。
注意:使用 LIMIT 避免递归死循环,隐藏着一个重要的前提,就是递归出来的结果集要能够满足使用要求!什么意思呢?就拿这个例子来说,假设在外层 SELECT 语句中再加一个 ORDER BY 条件,想想这个死循环能被 LIMIT 破解吗?不能!因为在最终的结果集出来之前,是不能 ORDER BY 的,最终结果集什么时候能出来呢?永远也出不来,因为它是个死循环。
该特性最好只在测试环境中使用,官方不建议在生产环境中使用,因为在其他系统中,都有可能因为实现原理和工作机制的不同,导致结果集的偏差。
Data-Modifying Statements in WITH
WITH 中不仅可以使用 SELECT 查询数据,还可以使用 INSERT,UPDATE,DELETE 等修改数据,用法大同小异。
看例子:
WITH moved_rows AS (
DELETE FROM products
WHERE
"date" >= '2010-10-01' AND
"date" < '2010-11-01'
RETURNING *
)
INSERT INTO products_log
SELECT * FROM moved_rows;
该 SQL 的含义是从 products 表中删除部分数据,然后把删除信息记录在 product_log 表中。
该 WITH 中使用 RETURNING 子句返回被 DELETE 的数据(不是products表的原始数据)给 moved_rows ,RETURNING 后面是 products 表列名,可以指定返回部分列(逗号分割),也可以使用 * 表示返回所有列。如果 WITH 中没有 RETURNING 子句,而与 WITH 平级的 SQL 又引用了 moved_rows ,整个 SQL 就会报错。
没有 RETURNING 的例子:
WITH t AS (
DELETE FROM foo
)
DELETE FROM bar;
这个 WITH 中可以没有 RETURNING 子句,因为外面没有引用 t 的主SQL 。这样写SQL没有什么意义,它会删除 foo 表和 bar 表中的所有数据,但是返回给客户端的影响行数只有 "DELETE FROM bar" 的结果,没有 "DELETE FROM foo" 的结果。
小结:在 WITH 中修改数据,是一个完全独立于主SQL的部分,也就是说,无论主SQL是否依赖于 WITH 的输出,WITH 中的修改操作都会执行。这不同于在 WITH 中使用 SELETCT:如果主SQL依赖 WITH 输出,SELECT 就执行,如果不需要 SELECT 就不执行。
RETURNING
WITH中可以有多处修改数据的操作,这些操作和主SQL是并发执行的,因此他们的执行顺序是不可预测的。又因为它们属于同一个SQL,在一个快照里面执行,所以它们使用的都是目标表的原始数据,彼此之间也看不到各自对目标表的影响。为了避免这种不可预测的结果,PostgreSQL 就利用了 RETURNING 子句来交流彼此对目标表的 "改变" ,换句话说就是,RETURNING 数据是目标表发生改变的那部分数据,是改变后的新数据。
来个例子:
WITH t AS (
UPDATE products SET price = price * 1.05
RETURNING *
)
SELECT * FROM products;
WITH 子句对 products 进行了 UPDATE 操作,但是主SQL查询出来的依然是未修改前的 products 数据。
再来一个:
WITH t AS (
UPDATE products SET price = price * 1.05
RETURNING *
)
SELECT * FROM t;
这个主SQL查询出来的就是修改后发生变化的数据。
多次修改同一行数据
在同一个SQL中,不支持多次 UPDATE 同一行数据。多次 UPDATE 时,只有一个 UPDATE 可以被执行,但具体是哪个无法预测。这个规则同样适用于 DELETE ,不能 DELETE 一个已经 UPDATE 的数据。
因此,应该避免在同一个SQL中,针对同一行进行多次修改操作的行为。尤其是在 WITH 子句中,主SQL和 WITH 的多个 CTE 很容易组合出"对同一行数据多次修改"的情况,一旦出现这种情况,结果是不可预测的。
参考资料:
PostgreSQL 9.1.19 Documentation
PostgreSQL: WITH Queries (Common Table Expressions)的更多相关文章
- Using Recursive Common table expressions to represent Tree structures
http://www.postgresonline.com/journal/archives/131-Using-Recursive-Common-table-expressions-to-repre ...
- The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP or FOR XML is also specified.
https://stackoverflow.com/questions/30045871/sorting-the-view-based-on-frequency-in-sql-server Just ...
- Common Table Expressions (CTE)
子查询有时使用起来很麻烦,因为所有的过滤和匹配逻辑都必须集成到子查询表达式中.如果只需要执行一个任务,且只需要使用一次杳询表达式,子查询是很好的选择.但子查询不能被重用,也不能很好地支持多个需求.这个 ...
- CTE(Common Table Expression) 公用表表达式
在编写T-SQL代码时,往往需要临时存储某些结果集.前面我们已经广泛使用和介绍了两种临时存储结果集的方法:临时表和表变量.除此之外,还可以 使用公用表表达式的方法.公用表表达式(Common Tabl ...
- SQLServer中的CTE(Common Table Expression)通用表表达式使用详解
概述 我们经常会编写由基本的 SELECT/FROM/WHERE 类型的语句派生而来的复杂 SQL 语句.其中一种方案是需要编写在 FROM 子句内使用派生表(也称为内联视图)的 Transact-S ...
- 通用表表达式(Common Table Expression)
问题:编写由基本的 SELECT/FROM/WHERE 类型的语句派生而来的复杂 SQL 语句. 方案1:编写在From子句内使用派生表(内联视图)的T-SQL查询语句. 方案2:使用视图 方案3:使 ...
- with as (cte common table expression) 公共表表达式
SQL中 with as 的用法——使用公用表表达式(CTE) 公用表表达式 (CTE) 可以认为是在单个 SELECT.INSERT.UPDATE.DELETE 或 CREATE VIEW 语句的 ...
- [SoapUI] Common XPath expressions
选取节点 表达式 描述 nodename 选取此节点的所有子节点. / 从根节点选取. // 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置. . 选取当前节点. .. 选取当前节点的父节点 ...
- 学习笔记之Nearest-Neighbour Searching with PostGIS
PostgreSQL: Documentation: 10: 7.8. WITH Queries (Common Table Expressions) https://www.postgresql.o ...
随机推荐
- C++语言基础(22)-转换构造函数和类型转换函数
一.转换构造函数 将其它类型转换为当前类类型需要借助转换构造函数(Conversion constructor).转换构造函数也是一种构造函数,它遵循构造函数的一般规则.转换构造函数只有一个参数. # ...
- 转载:JMeter压力测试入门教程[图文]
JMeter压力测试入门教程[图文] Apache JMeter是Apache组织开发的基于Java的压力测试工具.用于对软件做压力测试,它最初被设计用于Web应用测试但后来扩展到其他测试领域. 它可 ...
- JVM Specification 9th Edition (1) Cover
这个就是Java虚拟机规范第9版的网页版封面了,上面是4个大牛的名字,先来了解以下吧,万一那天有幸遇见呢. Tim Lindholm Frank Yellin Gilad Bracha Alex Bu ...
- Buffer ByteBuffer 缓冲区
http://blog.sina.com.cn/s/blog_4150f50c0100gfa3.html
- Tuning 04 Sizing the Buffer Cache
Buffer Cache 特性 The buffer cache holds copies of the data blocks from the data files. Because the bu ...
- Text类型的字段进行数据替换
一.text不大于8000 varchar和nvarchar类型是支持replace函数的,所以如果你的text不超过8000,可以先转换成前面两种类型再使用replace. UPDATE News ...
- 出售 unity3d串口插件
出售unity3d串口插件 利用C++编写,解决了mono库 serialport的bug. serialport串口的bug地方在于: 1.有一些数据无法收到. 2.会丢失第一个字节. 3.延迟 我 ...
- 设置grid高度
$("#jqxSalaryCalculation").jqxGrid({ height: $("#jqxTree").height() - 73 });
- cout显示Mat类对象报错Access Violation
AV(Access Violation)错误:非法访问. image_match.exe 中的 0x0000002a 处有未经处理的异常: 0xC0000005: Access violation 程 ...
- Json对象与Json字符串互转(4种转换方式) jquery 以及 js 的方式
http://blog.csdn.net/zero_295813128/article/details/51545467