只是希望证实我的观察并得到有关为什么会发生这种情况的解释。
我有一个函数定义为:
CREATE OR REPLACE FUNCTION "public"."__post_users_id_coin" ("coins" integer, "userid" integer) RETURNS TABLE (id integer) AS '
UPDATE
users
SET
coin = coin + coins
WHERE
userid = users.id
RETURNING
users.id' LANGUAGE "sql" COST 100 ROWS 1000
VOLATILE
RETURNS NULL ON NULL INPUT
SECURITY INVOKER
Run Code Online (Sandbox Code Playgroud)
当我从 CTE 调用此函数时,它执行 SQL 命令但不触发该函数,例如:
WITH test AS
(SELECT * FROM __post_users_id_coin(10, 1))
SELECT
1 -- Select 1 but update not performed
Run Code Online (Sandbox Code Playgroud)
另一方面,如果我从 CTE 调用该函数,然后选择 CTE 的结果(或在没有 CTE 的情况下直接调用该函数),它将执行 SQL 命令并触发该函数,例如:
WITH test AS
(SELECT * FROM __post_users_id_coin(10, 1))
SELECT
*
FROM
test -- Select result and update performed
Run Code Online (Sandbox Code Playgroud)
或者
SELECT * FROM __post_users_id_coin(10,1)
Run Code Online (Sandbox Code Playgroud)
由于我并不真正关心函数的结果(只需要它来执行更新),是否有任何方法可以在不选择 CTE 结果的情况下使其工作?
ype*_*eᵀᴹ 12
这是一种预期的行为。CTE 已实现,但有一个例外。
如果父查询中未引用 CTE,则它根本不会具体化。例如,您可以试试这个,它会运行良好:
WITH not_executed AS (SELECT 1/0),
executed AS (SELECT 1)
SELECT * FROM executed ;
Run Code Online (Sandbox Code Playgroud)
从 Craig Ringer 的博客文章中的评论中复制的代码:
PostgreSQL 的 CTE 是优化栅栏。
在尝试这个和几个类似的查询之前,我认为例外是:“当父查询或另一个 CTE 中没有引用 CTE 并且没有引用另一个 CTE 时”。因此,如果您希望执行 CTE 但查询结果中未显示结果,我认为这将是一种解决方法(在另一个 CTE 中引用它)。
但可惜,它不像我预期的那样工作:
WITH test AS
(SELECT * FROM __post_users_id_coin(10, 1)),
execute_test AS
(TABLE test)
SELECT 1 ; -- no, it doesn't do the update
Run Code Online (Sandbox Code Playgroud)
因此,我的“例外规则”是不正确的。当一个 CTE 被另一个 CTE 引用而父查询没有引用它们时,情况会更加复杂,我不确定会发生什么以及 CTE 何时具体化。我也无法在文档中找到此类案例的任何参考。
我没有看到比使用您已经建议的更好的解决方案:
SELECT * FROM __post_users_id_coin(10, 1) ;
Run Code Online (Sandbox Code Playgroud)
或者:
WITH test AS
(SELECT * FROM __post_users_id_coin(10, 1))
SELECT *
FROM test ;
Run Code Online (Sandbox Code Playgroud)
如果该函数更新多行并且您1在结果中获得多行(带有),您可以聚合以获得单行:
SELECT MAX(1) AS result FROM __post_users_id_coin(10, 1) ;
Run Code Online (Sandbox Code Playgroud)
但我更希望返回执行更新的函数的结果,以SELECT *您的示例为例,因此无论调用此查询,都知道是否有更新以及表中的更改是什么。
这是预期的、记录在案的行为。
修改数据的报表中
WITH都只执行一次,并且 总是完成,独立的是否主要查询读取所有(或任何)的输出。请注意,这与SELECTin的规则不同WITH:如上一节所述, aSELECT的执行仅在主查询需要其输出时进行。
大胆强调我的。“数据修改”是INSERT,UPDATE和DELETE查询。(相对于SELECT.)。手册再次:
你可以使用(修改数据的语句
INSERT,UPDATE或DELETE)的WITH。
CREATE OR REPLACE FUNCTION public.__post_users_id_coin (_coins integer, _userid integer)
RETURNS TABLE (id integer) AS
$func$
UPDATE users u
SET coin = u.coin + _coins -- see below
WHERE u.id = _userid
RETURNING u.id
$func$ LANGUAGE sql COST 100 ROWS 1000 STRICT;
Run Code Online (Sandbox Code Playgroud)
我删除了默认(噪音)子句,
STRICT是RETURNS NULL ON NULL INPUT.
以某种方式确保参数名称与列名称不冲突。我在前面加上_,但这只是我个人的喜好。
如果coin可以,NULL我建议:
SET coin = CASE WHEN coin IS NULL THEN _coins ELSE coin + _coins END
Run Code Online (Sandbox Code Playgroud)
如果users.id是主键,那么既没有RETURNS TABLE也没有ROWs 1000任何意义。只能更新/返回一行。但这都不是重点。
RETURNING如果您无论如何都要忽略调用中的返回值,那么使用子句并从函数中返回值是没有意义的。SELECT * FROM ...如果无论如何都忽略它们,分解返回的行也是没有意义的。
只需返回一个标量常量 ( RETURNING 1),将函数定义为RETURNS int(或RETURNING完全删除并使其成为RETURNS void)并使用SELECT my_function(...)
自从你 ...
不在乎结果
.. 只是SELECT一个常数形式的 CTE。只要在外部SELECT(直接或间接)中引用它,它就可以保证被执行。
WITH test AS (SELECT __post_users_id_coin(10, 1))
SELECT 1 FROM test;
Run Code Online (Sandbox Code Playgroud)
如果您确实有一个设置返回函数并且仍然不关心输出:
WITH test AS (SELECT * FROM __post_users_id_coin(10, 1))
SELECT 1 FROM test LIMIT 1;
Run Code Online (Sandbox Code Playgroud)
无需返回超过 1 行。该函数仍被调用。
最后,不清楚为什么您需要 CTE 开始。可能只是一个概念证明。
密切相关:
SO的相关答案:
并考虑:
| 归档时间: |
|
| 查看次数: |
2418 次 |
| 最近记录: |