CTE内から呼び出されたときにPostgreSQL関数が実行されない


14

私の観察を確認し、なぜこれが起こっているのかについて説明を得たいと思っています。

私は次のように定義された関数を持っています:

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

CTEからこの関数を呼び出すと、SQLコマンドは実行されますが、関数はトリガーされません。次に例を示します。

WITH test AS
(SELECT * FROM __post_users_id_coin(10, 1))

SELECT
1 -- Select 1 but update not performed

一方、CTEから関数を呼び出してからCTEの結果を選択する(またはCTEを使用せずに関数を直接呼び出す)と、SQLコマンドが実行され、関数がトリガーされます。次に例を示します。

WITH test AS
(SELECT * FROM __post_users_id_coin(10, 1))

SELECT
*
FROM
test -- Select result and update performed

または

SELECT * FROM __post_users_id_coin(10,1)

私は実際に関数の結果を気にしないので(更新を実行するために必要なだけです)、CTEの結果を選択せず​​にこれを機能させる方法はありますか?

回答:


11

それは一種の予想される動作です。CTEは具体化されますが、例外があります。

親クエリでCTEが参照されていない場合、CTEはまったく実体化されません。たとえば、これを試してみると問題なく実行できます。

WITH not_executed AS (SELECT 1/0),
     executed AS (SELECT 1)
SELECT * FROM executed ;

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

したがって、私の「例外規則」は正しくありません。CTEが別のCTEによって参照され、それらのいずれも親クエリによって参照されない場合、状況はより複雑であり、CTEが具体化される時期と正確な理由はわかりません。ドキュメントにもそのようなケースのリファレンスは見つかりません。


あなたがすでに提案したものを使用するよりも良い解決策はありません:

SELECT * FROM __post_users_id_coin(10, 1) ;

または:

WITH test AS
    (SELECT * FROM __post_users_id_coin(10, 1))
SELECT *
FROM test ;

関数が複数の行を更新1し、結果で多くの行(を含む)を取得する場合、単一の行を取得するために集約できます。

SELECT MAX(1) AS result FROM __post_users_id_coin(10, 1) ;

しかしSELECT *、例として、更新を行う関数の結果が返されるようにしたいので、このクエリを呼び出すものはすべて、更新があるかどうか、テーブルの変更が何であるかを知っています。



4

これは予想される、文書化された動作です。

トムレーンはここで説明します。

こちらのマニュアルに記載されています:

のデータ変更ステートメントは、プライマリクエリがすべての(または実際には)出力を読み取るかどうかに関係なく、1 WITH回だけ実行され、常に完了まで実行され ます。これはSELECTin のルールとは異なることに注意してください。WITH前のセクションで述べたように、aの実行はSELECT、プライマリクエリがその出力を要求する場合にのみ実行されます

大胆な強調鉱山。「データ変更」はINSERTUPDATEおよびDELETEクエリです。(SELECT。とは対照的)。もう一度マニュアル:

あなたは、データ変更ステートメント(使用することができINSERTUPDATEまたは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;

デフォルト(ノイズ)句を削除し STRICTましたRETURNS NULL ON NULL INPUT。これはの短い同義語です

パラメータ名が列名と競合しないことを確認してください。を先頭に付けましたが_、それは私の個人的な好みです。

coin可能であればNULL私は提案する:

SET    coin = CASE WHEN coin IS NULL THEN _coins ELSE coin + _coins END

users.idが主キーの場合、意味も意味もありRETURNS TABLEませんROWs 1000。更新/返されるのは1行のみです。しかし、それはすべて重要な点です。

適切な電話

RETURNINGとにかく呼び出しで返された値を無視する場合は、句を使用して関数から値を返すことは意味がありません。SELECT * FROM ...とにかくそれらを無視する場合、返された行を分解することも意味がありません。

スカラー定数(RETURNING 1)を返し、関数を次のように定義RETURNS int(またはRETURNING完全に削除して作成RETURNS void)し、SELECT my_function(...)

解決

あなたが...

結果を気にしない

.. SELECTCTEの単なる定数。外部でSELECT(直接的または間接的に)参照されている限り、実行されることが保証されています。

WITH test AS (SELECT __post_users_id_coin(10, 1))
SELECT 1 FROM test;

実際に集合を返す関数があり、それでも出力を気にしない場合:

WITH test AS (SELECT * FROM __post_users_id_coin(10, 1))
SELECT 1 FROM test LIMIT 1;

複数の行を返す必要はありません。関数はまだ呼び出されています。

最後に、最初にCTEが必要な理由は不明です。おそらく単なる概念実証です。

密接に関連する:

SOに関する関連回答:

そして考慮してください:


素晴らしく、大ファンであり、あなたの答えもアーウィンに与えて光栄です。同じラッピング機能内でトランザクションを行うINSERT前にCTEを使用していUPDATEます。
アンディ

いいね ただ、AQ:さtestWITH test AS (SELECT * FROM __post_users_id_coin(10, 1)) SELECT ... LIMIT 1;CTEを修正するかどうかと考えますか?
ypercubeᵀᴹ

@ypercubeᵀᴹ:SELECTCTEの用語によると、A は「データ変更」ではありません。上記の説明を追加しました。カーテンの後ろのデータを変更する関数にコードを追加する場合、それはユーザーの責任です。
アーウィンブランドステッター
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.