我有一张表parent
:
create table parent
(
identifier serial primary key,
name text
);
现在我有以下两个功能:
create or replace function test_a() returns integer
as $$
insert into parent(name) values('testing') returning identifier;
$$ language sql;
create or replace function test_b() returns setof parent
as $$
delete from parent where identifier = (test_a()) returning parent.*;
$$ language sql;
现在我做了:
select * from test_b();
这不会返回任何行。这是有道理的,delete
无法看到insert
插入的行,因为此行刚刚插入,因此不会在执行select * from test_b()
查询时定义的快照中。
遵循此逻辑,该行应位于表中。因此,让我们检查parent
:
select * from parent;
这不会返回任何行。我不明白这一点。为什么是这样?如果我的推理有缺陷(你能解释原因),为什么select * from test_b()
然后不返回行?这似乎是一个矛盾的终点。
更新
如果我有以下两个功能,那就完全不同了:
create or replace function test_a() returns integer
as $$
insert into parent(name) values('testing') returning identifier;
$$ language sql;
create or replace function test_b() returns setof parent
as $$
delete from parent where identifier = (select test_a()) returning parent.*;
$$ language sql;
请注意(select test_a())
是唯一的区别。
现在select * from test_b()
仍然不返回行,但后续select * from parent
返回我们插入的一行。根据我对上述快照范围的推理,这是有道理的。
= (test_a())
和= (select test_a())
之间是否存在差异?
答案 0 :(得分:2)
我毫不犹豫地将此作为答案发布,因为我不知道你的问题的答案,而且我确信别人能够解释为什么会这样。但我发现它非常有趣,所以我玩了一下,这就是我发现的。
EXPLAIN SELECT * FROM parent WHERE identifier = test_a();
Seq Scan on parent (cost=0.00..26.20 rows=1 width=12)
Filter: (identifier = test_a())
VS
EXPLAIN SELECT * FROM parent WHERE identifier = (SELECT test_a());
Seq Scan on parent (cost=0.26..2.46 rows=1 width=12)
Filter: (identifier = $0)
InitPlan 1 (returns $0)
-> Result (cost=0.00..0.26 rows=1 width=0)
(我已经放弃了PK)
(SELECT test_a())
是子查询。它被预先解决一次,其结果用于随后的其他记录的顺序扫描。
当您说identifier = test_a()
时,因为test_a()函数是易失性的,所以它将针对顺序扫描中读取的每个记录执行。 (如果函数是不可变的,则对test_1a()的调用将替换为不可变函数的值。)
现在试试这个:
SELECT * FROM test_b()
几次SELECT * FROM parent
- 您发现没有创建任何内容。SELECT * FROM test_a()
- 仅创建一条记录SELECT * FROM test_b()
几次SELECT * FROM parent
- 您应该看到,您现在不仅每次在步骤5中运行查询时都有记录,而且还有更多记录。我的解释:当你运行test_b()时没有数据,从来没有达到调用test_a()的点 - 因为它没有提前解决,而是在为每条记录进行测试时解决了。由于没有记录,因此永远不会调用test_a()。现在您创建一条记录,然后再次调用test_b()。这次将单个记录与对test_a()的调用结果进行比较。再次运行它,并将两个记录与test_a()调用的结果进行比较(因此它被调用两次,再创建两个记录)。再运行几次,每次表中的记录数量翻倍。
如前所述,(SELECT test_a())
版本由于使用子查询,因此会提前解析值。因此,当您调用test_b()时,它首先调用test_a(),创建一条记录,即使parent
中还没有现有记录可供阅读。因此,该版本可以在空表中创建记录,而另一个版本不会创建任何记录(如果不存在)。