Créons une fonction qui a un effet secondaire afin que nous puissions voir combien de fois elle est exécutée:
CREATE OR REPLACE FUNCTION test.this_here(val integer)
RETURNS numeric
LANGUAGE plpgsql
AS $function$
BEGIN
RAISE WARNING 'I am called with %', val;
RETURN sqrt(val);
END;
$function$;
Et puis appelez cela comme vous le faites:
SELECT this_here(i) FROM generate_series(1,10) AS t(i) WHERE this_here(i) < 2;
WARNING: I am called with 1
WARNING: I am called with 1
WARNING: I am called with 2
WARNING: I am called with 2
WARNING: I am called with 3
WARNING: I am called with 3
WARNING: I am called with 4
WARNING: I am called with 5
WARNING: I am called with 6
WARNING: I am called with 7
WARNING: I am called with 8
WARNING: I am called with 9
WARNING: I am called with 10
this_here
──────────────────
1
1.4142135623731
1.73205080756888
(3 rows)
Comme vous le voyez, la fonction est appelée au moins une fois (à partir de la WHERE
clause), et lorsque la condition est vraie, une fois de plus pour produire la sortie.
Pour éviter la deuxième exécution, vous pouvez faire ce que suggère Edgar - à savoir envelopper la requête et filtrer l'ensemble de résultats:
SELECT *
FROM (SELECT this_here(i) AS val FROM generate_series(1,10) AS t(i)) x
WHERE x.val < 2;
WARNING: I am called with 1
... every value only once ...
WARNING: I am called with 10
Pour vérifier plus en détail comment cela fonctionne, on peut y aller pg_stat_user_functions
et vérifier calls
(étant donné track_functions
est réglé sur «tous»).
Essayons avec quelque chose qui n'a aucun effet secondaire:
CREATE OR REPLACE FUNCTION test.simple(val numeric)
RETURNS numeric
LANGUAGE sql
AS $function$
SELECT sqrt(val);
$function$;
SELECT simple(i) AS v
FROM generate_series(1,10) AS t(i)
WHERE simple(i) < 2;
-- output omitted
SELECT * FROM pg_stat_user_functions WHERE funcname = 'simple';
-- 0 rows
simple()
est en fait trop simple pour pouvoir être inséré , il n'apparaît donc pas dans la vue. Rendons-le non-inlinable:
CREATE OR REPLACE FUNCTION test.other_one(val numeric)
RETURNS numeric
LANGUAGE sql
AS $function$
SELECT 1; -- to prevent inlining
SELECT sqrt(val);
$function$;
SELECT other_one(i) AS v
FROM generate_series(1,10) AS t(i)
WHERE other_one(i) < 2;
SELECT * FROM pg_stat_user_functions ;
funcid │ schemaname │ funcname │ calls │ total_time │ self_time
────────┼────────────┼───────────┼───────┼────────────┼───────────
124311 │ test │ other_one │ 13 │ 0.218 │ 0.218
SELECT *
FROM (SELECT other_one(i) AS v FROM generate_series(1,10) AS t(i)) x
WHERE v < 2;
SELECT * FROM pg_stat_user_functions ;
funcid │ schemaname │ funcname │ calls │ total_time │ self_time
────────┼────────────┼───────────┼───────┼────────────┼───────────
124311 │ test │ other_one │ 23 │ 0.293 │ 0.293
En apparence, l'image est la même avec ou sans effets secondaires.
Changer other_one()
pour IMMUTABLE
changer le comportement (peut-être surprenant) au pire, car il sera appelé 13 fois dans les deux requêtes.
STABLE
/IMMUTABLE
ouVOLATILE
?