我有一个parent
和child
表格如下:
create table parent
(
identifier serial primary key,
name text
);
create table child
(
identifier serial primary key,
name text, parent_identifier integer references parent
);
我创建了两个实用程序函数来将parent
和child
行格式化为JSON对象:
create function format(child) returns json
as $$
select json_build_object('identifier', $1.identifier, 'name', $1.name)
$$ language sql stable;
create function format(parent) returns json
as $$
select json_build_object('identifier', $1.identifier, 'name', $1.name,
'children', array(select format(child) from child where parent_identifier = $1.identifier))
$$ language sql stable;
让我们测试一下:
insert into parent(name) values('first parent');
insert into parent(name) values('second parent');
insert into child(name, parent_identifier) values('first child first parent', (select identifier from parent where name = 'first parent'));
insert into child(name, parent_identifier) values('second child first parent', (select identifier from parent where name = 'first parent'));
insert into child(name, parent_identifier) values('first child second parent', (select identifier from parent where name = 'second parent'));
select format(parent) from parent;
这将返回以下JSON对象:
{
"identifier":5,
"name":"first parent",
"children":[
{
"identifier":7,
"name":"first child first parent"
},
{
"identifier":8,
"name":"second child first parent"
}
]
}
{
"identifier":6,
"name":"second parent",
"children":[
{
"identifier":9,
"name":"first child second parent"
}
]
}
大!但是,这有一个大问题:如果另一个事务在我们的insert
和select
查询之间进行了一些更改,则select
查询会完全返回我们刚插入的内容。我们可以通过将事务隔离级别设置为repeatable read
来解决此问题,但这会产生性能成本和其他缺点(我们可能需要重试)。
所以我想在一个CTE中重写上面的查询。如果我没有弄错的话,这不会受到这种并发问题的影响。我开始如下:
with
parents as
(
insert into parent(name)
select 'first parent'
union all
select 'second parent'
returning parent.identifier, parent.name
),
children as
(
insert into child(name, parent_identifier)
select 'first child first parent', identifier from parents where name = 'first parent'
union all
select 'second child first parent', identifier from parents where name = 'first parent'
union all
select 'first child second parent', identifier from parents where name = 'second parent'
)
select format(parents::parent) from parents;
这不能按预期工作。它返回以下JSON对象:
{
"identifier":7,
"name":"first parent",
"children":[]
}
{
"identifier":8,
"name":"second parent",
"children":[]
}
如您所见,不包括儿童。经过一些阅读,我明白了发生了什么。 CTE适用于在查询开始之前创建的快照。在format(parent)
中,我们正在执行select format(child) from child where parent_identifier = $1.identifier)
,但这不会产生任何子行,因为子行不在快照中。所以我的问题与此无关,正如我所理解的那样。
当然,如果我在主查询中简单地执行json_build_object
内容,与format
函数完全相同,我可以轻松解决这个问题,但后来我复制了代码。我在其他查询中也使用这些format
函数,与此问题无关。理想情况下,我想在我的解决方案中避免代码重复。所以我想继续使用它们,可能需要先重构它们才能在这个问题的场景中使用它们。
我现在很困惑。我真的想继续使用CTE(因此我可以避免将事务隔离级别设置为repeatable read
),但我无法找到重新计算format(parent)
和{{1}的因素的方法函数和/或CTE,所以我不会在所有地方重复代码重复。是否有一个聪明的灵魂与一些聪明的想法?
请注意,我使用的是PostgreSQL 10.1。请在这里找到一个小提琴:http://sqlfiddle.com/#!17/a251d/2
关于Laurenz Albe回答的更新
背景:https://stackoverflow.com/revisions/48152380/1
在上面的问题中,我确实简化了我的情况。让我更仔细地解释真实场景,而不会涉及太多令人困惑的细节。
在该方案中,用户正在为特定日期范围(例如2018年1月)提供数据(= format(child)
及其对应的parents
)。另外,我不只是在插入,我实际上正在做孤立行的upserts和删除。因此,场景很简单:客户端正在替换给定日期范围内的所有数据。
如果我在upserts和deletes之后执行children
,则其他一些客户端可能会更改其间的重叠日期范围。在这种情况下,我返回客户端提供的不同结果,如果客户端未正确实现,可能会引入错误。因此,这就是为什么我认为插入和选择需要成为事务隔离级别设置为select format(parent) from parent where <parent is in date range as provided>
的同一事务的一部分。
然而,我开始思考一个单一的,肥胖的CTE,因此我的问题。
我希望这可以澄清这种情况。
答案 0 :(得分:1)
正如您所注意到的那样,您无法在主SELECT
中看到CTE中修改的行。这is documented:
WITH
中的子语句彼此同时执行 并与主要查询。因此,在使用数据修改语句时 在WITH
中,指定更新实际发生的顺序是 不可预知的。所有语句都使用相同的快照执行 (见Chapter 13),所以他们不能“看到”彼此 对目标表的影响。这减轻了影响 行更新的实际顺序的不可预测性,并且意味着RETURNING
数据是在不同之间传递变化的唯一方式WITH
子语句和主要查询。
所以你应该使用RETURNING
。
我想最简单的方法是不使用函数,而是在主查询中执行json_build_object
并使其在CTE parents
和children
上运行。