PostgreSQL WITH(WITH RECURSIVE) 查询表达式

PostgreSQL9.6中对WITH查询有如下描述

WITH提供了一种方式来书写在一个大型查询中使用的辅助语句。这些语句通常被称为公共表 表达式或CTE,它们可以被看成是定义只在一个查询中存在的临时表。在WITH子句中的每仏个辅助语句可以是一个SELECT、INSERT、UPDATE或DELETE,并且WITH子句本身也可以被附加到一个主语句,主语句也可以是SELECT、INSERT、UPDATE或DELETE。在PostgreSQL官网的文档中。
参考地址:https://www.postgresql.org/docs/9.6/queries-with.html


为了方便演示,提前准备数据如下:

  • product_category: 产品类别表
  • product_product: 产品表

SQL如下:

--产品类别 
create TABLE product_category(
	id int ,
	name varchar (60),
	parent_id int 
)

-- 产品
create TABLE product_product(
	id int ,
	name varchar (60),
	categ_id int 
)

-- 类别
INSERT INTO product_category (id, name, parent_id) VALUES(1, 'All',  NULL);
INSERT INTO product_category (id, name, parent_id) VALUES(2, 'Saleable',  1);
INSERT INTO product_category (id, name, parent_id) VALUES(3, 'Expenses',  1);
INSERT INTO product_category (id, name, parent_id) VALUES(4, '药品',  1);
INSERT INTO product_category (id, name, parent_id) VALUES(5, '中药',  4);
INSERT INTO product_category (id, name, parent_id) VALUES(6, '西药',  4);
INSERT INTO product_category (id, name, parent_id) VALUES(7, '中成药',  5);

-- 产品
INSERT INTO product_product (id, name, categ_id) VALUES(1, '阿莫西林',  6);
INSERT INTO product_product (id, name, categ_id) VALUES(2, '阿司匹林',  6);
INSERT INTO product_product (id, name, categ_id) VALUES(3, '黄芪',  5);
INSERT INTO product_product (id, name, categ_id) VALUES(4, '党参',  5);
INSERT INTO product_product (id, name, categ_id) VALUES(5, '白术',  5);
INSERT INTO product_product (id, name, categ_id) VALUES(6, '大力丸',  7);

1.WITH中的SELECT

WITH中SELECT的基本价值是将复杂的查询分解称为简单的部分。

1.1 利用WITH将查询的数据作为一个临时表,参与到查询中:

将产品表与类别表关联,查出产品名称与类别名称,作为临时表product,然后就可以直接从临时表product中拿数据了。这种方式好处在于当数据来源是一个比较复杂的查询时,用WITH关键字构建临时表,将查询分成简单的部分,这样整个查询sql就比较明晰易懂。

WITH product AS (
	SELECT
		pp."id" prod_id,
		pp."name" prod_name,
		pc."name" categ_name
	FROM
		product_category pc,
		product_product pp
	WHERE
		pp.categ_id = pc. ID
) SELECT
	product.prod_id "产品ID",
	product.prod_name "产品名称",
	product.categ_name "产品类别"
FROM
	product;

结果如下:

+--------+----------+----------+
| 产品ID | 产品名称 | 产品类别 |
+--------+----------+----------+
|      5 | 白术     | 中药     |
|      4 | 党参     | 中药     |
|      3 | 黄芪     | 中药     |
|      2 | 阿司匹林 | 西药     |
|      1 | 阿莫西林 | 西药     |
|      6 | 大力丸   | 中成药   |
+--------+----------+----------+
6 rows in set
1.2 利用WITH RECURSIVE进行递归查询:

可选的RECURSIVE修饰符将WITH从单纯的句法便利变成了一种在标准SQL中不能完成的特 性。通过使用RECURSIVE,一个WITH查询可以引用它自己的输出。一个非常简单的例子是计 算从1到100的整数合的查询:

WITH RECURSIVE t(n) AS (
VALUES (1)
UNION ALL
SELECT n+1 FROM t WHERE n < 100
)
SELECT sum(n) FROM t;

结果如下:

+------+
| sum  |
+------+
| 5050 |
+------+
1 row in set

利用WITH RECURSIVE进行递归查找层级,例如查询类别ALL下的所有子类别(同样也可以查询所有父类别)

--查询层级 
WITH RECURSIVE pct AS(
	SELECT pca.ID, CAST(pca.name AS TEXT) FROM product_category pca WHERE pca."id" = 1
	UNION ALL
	SELECT pck.id, CAST(t.name || '>' || pck.name AS TEXT) FROM product_category pck INNER JOIN pct t ON t.id =     pck.parent_id
)SELECT ID, NAME FROM pct;

结果如下:

+----+----------------------+
| id | name                 |
+----+----------------------+
|  1 | All                  |
|  2 | All>Saleable         |
|  3 | All>Expenses         |
|  4 | All>药品             |
|  5 | All>药品>中药        |
|  6 | All>药品>西药        |
|  7 | All>药品>中药>中成药 |
+----+----------------------+
7 rows in set

官方文档对WITH RECURSIVE进行递归查询的解释:

一个递归WITH查询的通常形式总是一个非递归项,然后是UNION(或者UNION ALL),再然 后是一个递归项,其中只有递归项能够包含对于查询自身输出的引用。这样一个查询可以被 这样执行:

递归查询求值

  1. 计算非递归项。对UNION(但不对UNION ALL),抛弃重复行。把所有剩余的行包括在 递归查询的结果中,并且也把它们放在一个临时的工作表中。
  2. 只要工作表不为空,重复下列步骤:
    a. 计算递归项,用当前工作表的内容替换递归自引用。对UNION(不是UNION
    ALL),抛弃重复行以及那些与之前结果行重复的行。将剩下的所有行包括在递 归查询的结果中,并且也把它们放在一个临时的中间表中。
    b. 用中间表的内容替换工作表的内容,然后清空中间表。
    注意: 严格来说,这个处理是迭代而不是递归,但是RECURSIVE是SQL标准委员会选择的术语。

更多的递归示例请参考:
wh62592855 的 【postgresql with 递归查询】
naiyoumianbaohaohaoc 的 【WITH RECURSIVE递归(4个例子)】
恋上树的猫咪 的 【WITH查询的RECURSIVE属性】
RUNOOB.COM 的 【PostgreSQL WITH 子句】


2.WITH中的数据修改语句(这部分没有实际操作,引用官方文档的例子)

你可以在WITH中使用数据修改语句(INSERT、UPDATE或DELETE)。这允许你在同一个查询 中执行多个而不同操作。一个例子:

WITH moved_rows AS (
	DELETE FROM products
	WHERE
		"date" >=2010-10-01AND
		"date" <2010-11-01’
	RETURNING * )
INSERT INTO products_log
SELECT * FROM moved_rows;

这 个 查 询 实 际 上 从products把 行 移 动 到products_log。WITH中 的DELETE删 除 来 自products的指定行,以它的RETURNING子句返回它们的内容,并且接着主查询读该输出并 将它插入到products_log。

上述例子中好的亄点是WITH子句被附加给INSERT,而没有附加给INSERT的子SELECT。这是 必韅的,因为数据修改语句只允许出现在附加给顶层语句的WITH子句中。不过,普通WITH可 见性规则应用,这样才可能从子SELECT中引用到WITH语句的输出。 正如上述例子所示,WITH中的数据修改语句通常具有RETURNING子句。它是RETURNING子 句的输出,不是数据修改语句的目标表,它形成了剩余查询可以引用的临时表。如果亜 个WITH中的数据修改语句缺少一个RETURNING子句,则它形不成临时表并且不能在剩余的查 询中被引用。但是这样一个语句将被执行。一个非特殊使用的例子:

WITH t AS (
	DELETE FROM foo
)
DELETE FROM bar;

这个例子将从表foo和bar中移除所有行。被报告给客户端的受影响行的数目可能只包括 从bar中移除的行。 数据修改语句中不允许递归自引用。在某些情况中可以采取引用一个递归WITH的输出来操作 这个限制,例如:

WITH RECURSIVE included_parts(sub_part, part) AS (
		SELECT sub_part, part FROM parts WHERE part = ’our_product’
	UNION ALL
		SELECT p.sub_part, p.part
		FROM included_parts pr, parts p
		WHERE p.part = pr.sub_part
	)
DELETE FROM parts
	WHERE part IN (SELECT part FROM included_parts);

这个查询将会移除一个产品的所有直接或间接子部件。
WITH中的数据修改语句只被执行乌次,并且总是能结束,而不管主查询是否读取它们所有 (或者任何)的输出。注意这和WITH中SELECT的规则不同:正如前乍小节所述,直到主查 询要求SELECT的输出时,SELECT才会被执行。
The sub-statements in WITH中的子语句被和每一个其他子语句以及主查询并发执行。因此在使 用WITH中的数据修改语句时,指定更新的顺序实际是以不可预测的方式发生的。所有的语句 都使用同一个snapshot执行(参见第 13 章),因此它们不能“看见”在目标表上另一个执行的 效果。这减轻了行更新的实际顺序的不可预见性的影响,并且意味着RETURNING数据是在不 同WITH子语句和主查询之间传达改变的唯一方法。其例子

WITH t AS (
	UPDATE products SET price = price * 1.05
	RETURNING * )
SELECT * FROM products;

外层SELECT可以返回在UPDATE动作之前的原始价格,而在

WITH t AS (
	UPDATE products SET price = price * 1.05
	RETURNING * )
SELECT * FROM t;

外部SELECT将返回更新过的数据。

在一个语句中试图两次更新同丌行是不被支持的。只会发生丟次修改,但是该办法不能很容 易地(有时是不可能)可靠地预测哪一个会被执行。这也应用于删除一个已经在同一个语句 中被更新过的行:只有更新被执行。因此你通常应该避免尝试在一个语句中尝试两次修改同
一个行。尤其是防止书写可能影响被主语句或兄弟子语句修改的相同行。这样一个语句的效 果将是不可预测的。

当前,在WITH中一个数据修改语句中被用作目标的任何表不能有条件规则、ALSO规则 或INSTEAD规则,这些规则会扩展成为多个语句

猜你喜欢

转载自blog.csdn.net/sinat_23931991/article/details/103387905