我一直认为CTE应该被视为内联视图宏。所以我的想法是:如果未引用/使用CTE,则不会执行。这只是一个定义,仅此而已。
但是,请采取以下查询:
create table t
(
id int primary key
);
with
a as
(
insert into t(id) values(1)
)
select false;
select * from t;
似乎在基于CTE的查询之后,select * from t
返回插入CTE中的元组。尽管没有使用CTE,为什么要插入这个元组?
这是设计还是规格?依赖这种行为是否安全?这允许执行多个查询,这些查询在一个查询中完全不相关。
这似乎与以下信息相矛盾:https://blog.2ndquadrant.com/postgresql-ctes-are-optimization-fences/#comment-19121
答案 0 :(得分:4)
语法 CTE的行为与任何其他表表达式相同。
在语义上它是不同的。 [在Postgres中] 始终执行一次,即使它被多次引用也是如此。
[在Postgres] CTE将作为优化障碍;查询术语不能在CTE和主查询之间移动(进入或退出)。
第二点和第三点可能会产生严重影响。由于屏障和恰好一次,CTE扫描很难利用隐式顺序或CTE内部的索引的存在。 CTE扫描或多或少的行为类似于无序表或物化视图上的顺序扫描。对于小型CTE,这将没有问题,因为可以使用散列连接。大型CTE需要实现+排序才能将CTE加入主查询。
答案 1 :(得分:0)
在postgres中,cte 不应该被视为内联视图,尽管将其视为存在于语句范围内的物化视图是有用的。如果在查询的另一部分中引用CTE,或者如果它更改数据(INSERT / UPDATE / DELETE),则将实现CTE。
因此,由于您的示例会更改数据,因此会对CTE进行评估,而您引用的链接的CTE不会更改数据。
而在其他数据库中,外部查询的谓词将由优化器推送到CTE,在postgresql中,CTE将完全实现。
e.g。
WITH cte AS (SELECT * FROM foo WHERE foo.bar = True)
SELECT * FROM cte WHERE cte.id > 10 AND cte.id < 20
比postgresql
慢SELECT * FROM (SELECT * FROM foo WHERE bar = TRUE) cte
WHERE cte.id > 10 AND cte.id < 20
这是在外部查询中具有可选或动态谓词的考虑因素。更快的CTE版本将是
WITH cte AS (SELECT * FROM foo WHERE foo.bar = True AND foo.id > 10 AND foo.id < 20)
SELECT * FROM cte
这是设计使然,您可以依靠此行为来创建优化障碍。
只要允许SELECT子句,就允许使用CTE。因此,可以在INSERT,UPDATE或DELETE语句中使用CTE。 我不确定这是否是SQL标准的一部分。
例如,在9.5版本和INSERT ... ON CONFLICT
语法的引入之前,我们可以使用CTE来执行UPSERT。 Here's a SO thread that illustrates with an example
还有第二种更有趣的CTE类型,即RECURSIVE CTE,其中CTE由迭代部分和应用部分的并集组成,可以处理迭代部分中生成的值。我不认为这种类型的查询无论如何 内联。