Question débutant:
J'ai une fonction coûteuse f(x, y)
sur deux colonnes x et y dans ma table de base de données.
Je veux exécuter une requête qui me donne le résultat de la fonction en tant que colonne et y met une contrainte, quelque chose comme
SELECT *, f(x, y) AS func FROM table_name WHERE func < 10;
Mais cela ne fonctionne pas, je vais donc devoir écrire quelque chose comme
SELECT *, f(x, y) AS func FROM table_name WHERE f(x, y) < 10;
Est-ce que cela exécutera la fonction coûteuse deux fois? Quelle est la meilleure façon de procéder?
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 clause WHERE
), 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 le jeu 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 comment cela fonctionne, on peut aller à pg_stat_user_functions
et y vérifier calls
(étant donné track_functions
est défini à 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, il peut donc être en ligne , donc il n'apparaît 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()
en IMMUTABLE
modifie le comportement (peut-être de façon surprenante), car il sera appelé 13 fois dans les deux requêtes.
Essayez de l'appeler à nouveau:
SELECT
*
FROM (
SELECT
*,
f(x, y) AS func
FROM table_name
) a
WHERE a.func < 10;