web-dev-qa-db-fra.com

Fonction PostgreSQL non exécutée quand appelée de l'intérieur CTE

J'espère juste confirmer mon observation et expliquer pourquoi cela se produit.

J'ai une fonction définie comme suit:

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

Lorsque j'appelle cette fonction d'un CTE, il exécute la commande SQL mais ne déclenche pas la fonction, par exemple:

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

SELECT
1 -- Select 1 but update not performed

D'autre part, si j'appelle la fonction d'un CTE, puis sélectionnez le résultat du CTE (ou appelez la fonction directement sans CTE) Il exécute la commande SQL et déclenche la fonction, par exemple :

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

SELECT
*
FROM
test -- Select result and update performed

ou alors

SELECT * FROM __post_users_id_coin(10,1)

Puisque je ne me soucie pas vraiment du résultat de la fonction (il suffit de la nécessité d'effectuer la mise à jour) est-il un moyen de l'obtenir pour que cela fonctionne sans choisir le résultat du CTE?

16
Andy

C'est un peu de comportement attendu. Les CTES sont matérialisés mais il y a une exception.

Si un CTE n'est pas référencé dans la requête mère, il n'est pas matérialisé du tout. Vous pouvez essayer cela par exemple et il fonctionnera bien:

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

Code copié à partir d'un commentaire de Craig Singer's Blog Post:
[.____] Les CTES de PostgreSQL sont des clôtures d'optimisation .


Avant d'essayer ceci et plusieurs requêtes similaires, je pensais que l'exception était: "Lorsqu'un CTE n'est pas référencé dans la requête mère ou dans un autre CTE et ne se réfère pas à un autre CTE". Donc, si vous souhaitez que le CTE soit exécuté, les résultats ne figurent pas dans le résultat de la requête, je pensais que ce serait une solution de contournement (le référençant dans un autre CTE).

Mais hélas, cela ne fonctionne pas Comme je m'attendais:

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

et par conséquent, ma "règle d'exception" n'est pas correcte. Lorsqu'un CTE est référencé par un autre CTE et aucun d'entre eux n'est référencé par la requête mère, la situation est plus compliquée et je ne sais pas exactement ce qui se passe et quand les CTES sont matérialisés. Je ne trouve aucune référence pour de tels cas dans la documentation non plus.


Je ne vois aucune solution meilleure que d'utiliser ce que vous avez déjà suggéré:

SELECT * FROM __post_users_id_coin(10, 1) ;

ou alors:

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

Si la fonction met à jour plusieurs rangées et que vous obtenez de nombreuses lignes (avec 1) Dans le résultat, vous pouvez regrouper pour obtenir une seule ligne:

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

mais je préférerais avoir les résultats de la fonction qui fait une mise à jour renvoyée, avec SELECT * Comme votre exemple, donc quels que soient les appels de cette requête sait s'il y avait des mises à jour et les changements dans la table.

12
ypercubeᵀᴹ