Jim*_*Bob 5 postgresql cte unique-constraint
出于性能原因,我正在编写一个大型的多步 CTE 。
在一个查询中,数据必须从一个表移动到另一个表,但移动的行数是不确定的,可能为零。
在后续表中,删除来自前一个查询的源,但必须在完成前一个查询之后。
最后,在上面的第二个查询完成后,必须写入行来代替已删除的行。
在前两个查询中,我使用 RETURNING 来强制执行顺序。
在第二个查询中,我确定第一个查询是由这个子查询完成的
(SELECT COUNT(*) FROM first_query) >= 0
Run Code Online (Sandbox Code Playgroud)
在第三个查询中,我确定第二个查询是由这个子查询完成的
SELECT EXISTS (SELECT 1 FROM second_query)
Run Code Online (Sandbox Code Playgroud)
确定第一个查询已完成的子查询是否正确?
用于确定必须返回行的第二个查询已完成最佳准确性、精度和性能的子查询?
使用上述子查询来强制执行顺序会导致重复的键值违规。
查询小节
WITH copy_to_other_table AS (
INSERT INTO other_table (column_a, column_b)
SELECT column_a, column_b
FROM main_table
WHERE column_a = $1::bigint
RETURNING *
),
main_table_deleted AS (
DELETE FROM main_table WHERE column_a = $1::bigint
AND (SELECT COUNT(*) FROM copy_to_other_table) >= 0
RETURNING *
)
INSERT INTO main_table (column_a, column_b)
SELECT column_a, column_b
FROM another_table WHERE column_a = $1::bigint
AND EXISTS (SELECT 1 FROM main_table_deleted)
Run Code Online (Sandbox Code Playgroud)
这是违反唯一约束的最终查询。
这应该可行,但我不确定这是否是效率方面的最佳选择:
WITH copy_to_other_table AS (
INSERT INTO other_table (column_a, column_b)
SELECT column_a, column_b
FROM main_table
WHERE column_a = 1
),
main_table_deleted AS (
DELETE
FROM main_table
WHERE column_a = 1
AND NOT EXISTS (SELECT 1 FROM another_table
WHERE column_a = 1
AND column_b = main_table.column_b)
)
INSERT INTO main_table (column_a, column_b)
SELECT column_a, column_b
FROM another_table WHERE column_a = 1
EXCEPT
SELECT column_a, column_b
FROM main_table WHERE column_a = 1 ;
Run Code Online (Sandbox Code Playgroud)
但是原始查询有什么问题?
首先,(SELECT COUNT(*) ...) >= 0
完全是多余的。一个count
总总是返回0以上的值,这样的条件是总是如此。
其次,根本不需要有任何条件,因为main
您想要复制到other
表中的所有行,您还希望它们从main
. 在删除它们之前没有理由“检查”它们是否已复制。所有 3 个子查询(2 个 CTE 和主查询)将“看到”具有相同行数和数据的相同表。
第三部分比较棘手。乍一看,第二(删除)和第三(插入)部分之间的“交互”似乎不需要检查。两者都在同一个表中,但如果在主查询之前执行第二个 cte,那么一切都应该很好。
唉,执行顺序不连续。来自Postgres 文档:
WITH 中的数据修改语句只执行一次,并且总是完成,与主查询是否读取所有(或实际上任何)其输出无关。请注意,这与 WITH 中的 SELECT 规则不同:如上一节所述,仅在主查询需要其输出时才会执行 SELECT。
WITH 中的子语句彼此并发执行,并与主查询同时执行。因此,在 WITH 中使用数据修改语句时,指定更新实际发生的顺序是不可预测的。所有语句都使用相同的快照执行(参见第 13 章),因此它们无法“看到”彼此对目标表的影响。这减轻了行更新实际顺序的不可预测性的影响,并且意味着
RETURNING
数据是在不同WITH
子语句和主查询之间传达更改的唯一方式。
作为测试,您可以更改 3 个子语句的顺序。结果将是相同的:
WITH main_table_deleted AS (
DELETE
FROM main_table
WHERE column_a = 1
AND NOT EXISTS (SELECT 1 FROM another_table
WHERE column_a = 1
AND column_b = main_table.column_b)
),
copy_to_other_table AS (
INSERT INTO other_table (column_a, column_b)
SELECT column_a, column_b
FROM main_table
WHERE column_a = 1
)
INSERT INTO main_table (column_a, column_b)
SELECT column_a, column_b
FROM another_table WHERE column_a = 1
EXCEPT
SELECT column_a, column_b
FROM main_table WHERE column_a = 1 ;
Run Code Online (Sandbox Code Playgroud)相关问题是何时检查唯一约束。我不是 100% 确定这些检查与 CTE 相结合的细节,但应在语句末尾检查唯一约束。似乎每个修改 cte 也同时检查它们。
(注意:老实说,这种行为似乎是一个错误 - 除非我错过了文档中的某些内容。)
关于您的最后一个问题,将隔离级别设置为SERIALIZABLE
不会解决问题,因为整个操作是一个语句,包含 3 个子语句。但是,您可以将操作拆分为 2 或 3 个语句,然后将它们一个接一个地执行。所以,第二个会看到第一个和第三个的结果,前两个的结果。(如果这样做,请将 2 或 3 个语句放在事务中,以将操作与其他正在执行的语句隔离开来。)
另一种方法 - 更接近您的原始查询 - 将使用该RETURNING
子句以特定顺序强制执行子语句,即第二个之后的第三个(第一个可以保持RETURNING
不变并同时执行)。在SQLFIdle-3 中测试:
WITH copy_to_other_table AS (
INSERT INTO other_table (column_a, column_b)
SELECT column_a, column_b
FROM main_table
WHERE column_a = 1
),
main_table_deleted AS (
DELETE FROM main_table WHERE column_a = 1
RETURNING *
)
INSERT INTO main_table (column_a, column_b)
SELECT column_a, column_b
FROM another_table WHERE column_a = 1
EXCEPT
(TABLE main_table_deleted EXCEPT TABLE another_table) ;
Run Code Online (Sandbox Code Playgroud)
或者通过首先执行删除(第二个)cte,然后RETURNIING
在其他两个中使用其输出来稍微改进:
WITH main_table_deleted AS (
DELETE FROM main_table WHERE column_a = 1
RETURNING *
),
copy_to_other_table AS (
INSERT INTO other_table (column_a, column_b)
TABLE main_table_deleted
)
INSERT INTO main_table (column_a, column_b)
SELECT column_a, column_b
FROM another_table WHERE column_a = 1
EXCEPT
(TABLE main_table_deleted EXCEPT TABLE another_table) ;
Run Code Online (Sandbox Code Playgroud)
这似乎是问题的原因,来自 ypercube 引用的同一页面:
不支持尝试在单个语句中更新同一行两次。只发生了一个修改,但要可靠地预测哪一个并不容易(有时是不可能的)。这也适用于删除已在同一语句中更新的行:仅执行更新。因此,您通常应该避免尝试在单个语句中两次修改单个行。特别要避免编写可能影响由主语句或同级子语句更改的相同行的 WITH 子语句。这种声明的影响是不可预测的。
约束
在测试更复杂的查询之后,似乎在子语句级别应用了唯一约束,而在语句级别应用了外键。
归档时间: |
|
查看次数: |
2226 次 |
最近记录: |