私の観察を確認し、これがなぜ起こっているのかについての説明を得たいと思っています。
次のように定義された関数があります。
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コマンドが実行され、does trigger関数が実行されます。 :
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の結果を選択せずにこれを機能させる方法はありますか?
それは一種の予想される行動です。 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 *
を例として返されるようにしたいので、このクエリを呼び出すと、更新があったかどうかと、テーブルの変更が何であるかがわかります。
これは予想される、文書化された動作です。
WITH
のデータ変更ステートメントは1回だけ実行され、常に完了します、プライマリクエリが出力のすべて(または実際にすべて)を読み取るかどうかに関係なく。これはSELECT
のWITH
のルールとは異なることに注意してください。前のセクションで説明したように、SELECT
の実行が行われますプライマリクエリがその出力を要求する限り。
大胆な強調鉱山。 「データ変更」は、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;
デフォルト(ノイズ)句を削除しました 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(...)
あなたから...
結果を本当に気にしない
.. CTEを構成する定数SELECT
だけ。外部SELECT
で(直接または間接に)参照されている限り、実行されることが保証されています。
WITH test AS (SELECT __post_users_id_coin(10, 1))
SELECT 1 FROM test;
実際にset-returning関数があり、それでも出力を気にしない場合:
WITH test AS (SELECT * FROM __post_users_id_coin(10, 1))
SELECT 1 FROM test LIMIT 1;
複数の行を返す必要はありません。関数はまだ呼び出されています。
最後に、そもそもCTEが必要な理由は明らかではありません。おそらく単なるコンセプトの証明です。
密接に関連:
SOの関連回答:
そして考慮してください: