Evaluation des fonctions Oracle et SQLServer dans les requêtes
-
06-07-2019 - |
Question
Disons que j'ai un appel de fonction sur une clause select ou Where dans Oracle, comme ceci:
select a, b, c, dbms_crypto.hash(utl_raw.cast_to_raw('HELLO'),3)
from my_table
Un exemple similaire peut être construit pour MS SQLServer.
Quel est le comportement attendu dans chaque cas?
La fonction HASH sera-t-elle appelée une fois pour chaque ligne de la table ou le système de gestion de base de données sera-t-il assez intelligent pour appeler la fonction une seule fois, puisqu'il s'agit d'une fonction à paramètres constants et non effets secondaires ?
Merci beaucoup.
La solution
La réponse pour Oracle est que cela dépend. La fonction sera appelée pour chaque ligne sélectionnée SAUF SI la fonction est marquée "Déterministe", auquel cas elle ne sera appelée qu'une fois.
CREATE OR REPLACE PACKAGE TestCallCount AS
FUNCTION StringLen(SrcStr VARCHAR) RETURN INTEGER;
FUNCTION StringLen2(SrcStr VARCHAR) RETURN INTEGER DETERMINISTIC;
FUNCTION GetCallCount RETURN INTEGER;
FUNCTION GetCallCount2 RETURN INTEGER;
END TestCallCount;
CREATE OR REPLACE PACKAGE BODY TestCallCount AS
TotalFunctionCalls INTEGER := 0;
TotalFunctionCalls2 INTEGER := 0;
FUNCTION StringLen(SrcStr VARCHAR) RETURN INTEGER AS
BEGIN
TotalFunctionCalls := TotalFunctionCalls + 1;
RETURN Length(SrcStr);
END;
FUNCTION GetCallCount RETURN INTEGER AS
BEGIN
RETURN TotalFunctionCalls;
END;
FUNCTION StringLen2(SrcStr VARCHAR) RETURN INTEGER DETERMINISTIC AS
BEGIN
TotalFunctionCalls2 := TotalFunctionCalls2 + 1;
RETURN Length(SrcStr);
END;
FUNCTION GetCallCount2 RETURN INTEGER AS
BEGIN
RETURN TotalFunctionCalls2;
END;
END TestCallCount;
SELECT a,TestCallCount.StringLen('foo') FROM(
SELECT 0 as a FROM dual
UNION
SELECT 1 as a FROM dual
UNION
SELECT 2 as a FROM dual
);
SELECT TestCallCount.GetCallCount() AS TotalFunctionCalls FROM dual;
Sortie:
A TESTCALLCOUNT.STRINGLEN('FOO')
---------------------- ------------------------------
0 3
1 3
2 3
3 rows selected
TOTALFUNCTIONCALLS
----------------------
3
1 rows selected
La fonction StringLen () a donc été appelée trois fois dans le premier cas. Maintenant, lors de l'exécution avec StringLen2 (), ce qui est noté déterministe:
SELECT a,TestCallCount.StringLen2('foo') from(
select 0 as a from dual
union
select 1 as a from dual
union
select 2 as a from dual
);
SELECT TestCallCount.GetCallCount2() AS TotalFunctionCalls FROM dual;
Résultats:
A TESTCALLCOUNT.STRINGLEN2('FOO')
---------------------- -------------------------------
0 3
1 3
2 3
3 rows selected
TOTALFUNCTIONCALLS
----------------------
1
1 rows selected
La fonction StringLen2 () n’a donc été appelée qu’une fois car elle a été marquée déterministe.
Pour une fonction non marquée déterministe, vous pouvez contourner ce problème en modifiant votre requête en tant que telle:
select a, b, c, hashed
from my_table
cross join (
select dbms_crypto.hash(utl_raw.cast_to_raw('HELLO'),3) as hashed from dual
);
Autres conseils
Pour SQL Server, il sera évalué pour chaque ligne.
Vous gagnerez BEAUCOUP mieux en exécutant la fonction une seule fois, en attribuant une variable et en utilisant la variable dans la requête.
réponse courte .... ça dépend.
Si la fonction accède aux données, ORACLE ne sait pas si elles seront identiques pour chaque ligne. Par conséquent, elle doit interroger chacune d'elles. Si, par exemple, votre fonction est juste un formateur qui renvoie toujours la même valeur, vous pouvez activer la mise en cache (en la définissant comme déterministe), ce qui peut vous permettre de n’appeler la fonction qu’une seule fois.
Vous voudrez peut-être examiner ORACLE WITH, sous-requête:
La clause WITH nom_requête vous permet attribuer un nom à un bloc de sous-requête. Vous peut alors référencer le bloc de sous-requête plusieurs endroits dans la requête par en spécifiant le nom de la requête. Oracle optimise la requête en traitant le nom de la requête sous forme de vue en ligne ou en tant que table temporaire
J'ai reçu le texte cité de ici , qui contient de nombreux exemples.