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 ...
随机推荐
- linux下Samba服务配置
SMB是基于客户机/服务器型的协议,因而一台Samba服务器既可以充当文件共享服务器,也可以充当一个Samba的客户端,例如,一台在Linux 下已经架设好的Samba服务器,windows客户端就可 ...
- myeclipse修改jsp文件的名称之后,再也打不开的解决方案
方案一.重启myEclipse 方案二. 删除对应workspace目录下 “.metadata\.plugins\org.eclipse.jst.jsp.core\jspsearch” 里的 *.i ...
- layui的单选框
<script type="text/html" id="radioTpl"> <input type="radio" n ...
- Jquery学习笔记(2)--五角星评分
网购五星评分模拟: <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...
- jQuery 实战读书笔记之第六章:事件本质
理解浏览器事件模型 understandEventModel.html 代码: <!DOCTYPE HTML> <html> <head> <title> ...
- 【Mac + Python3.6 + ATX基于facebook-wda】之IOS自动化(三):facebook-wda库--API学习以及附录:Github上对WDA的问题解答
下面简单介绍facebook-wda库--API的学习 import wda # debug模式,会在run运行时控制台生成消息 wda.DEBUG = False # False 关闭,True开启 ...
- Lua一般都用来干什么,有什么优点
Lua一般用于嵌入式应用,现在越来越多应用于游戏当中,魔兽世界,愤怒的小鸟都有用到. Lua极易嵌入到其他程序,可当做一种配置语言. 还有很多需要性能的地方,比如:游戏脚本,nginx,wiresha ...
- HttpClient+jsoup登录+解析 163邮箱
找了几个,只有这个靠谱,用的是httpclient4,另外还需要commons-lang和jsoup包 http://jsoup.org/ http://www.oschina.net/code/sn ...
- 无状态会话Bean、有状态会话Bean、CMP与BMP中,哪一种Bean不需要自己书写连接数据库的代码?
无状态会话Bean.有状态会话Bean.CMP与BMP中,哪一种Bean不需要自己书写连接数据库的代码? A.无状态会话Bean B.有状态会话Bean C.CMP D.BMP 解答:C
- Android Canvas和Paint基本使用
这篇文章主要介绍下画笔Paint和画布Canvas的基本使用 1.Paint 创建对象Paint mPaint = new Paint(); 常用的基本方法有 : mP ...