J'écris une procédure stockée qui nécessite beaucoup de conditionnement. Grâce à la connaissance générale du codage C # .NET selon laquelle les exceptions peuvent nuire aux performances, j'ai toujours évité de les utiliser également en PL/SQL. Mon conditionnement dans cette procédure stockée dépend principalement de l'existence ou non d'un enregistrement, ce que je pourrais faire de deux manières:
SELECT COUNT(*) INTO var WHERE condition;
IF var > 0 THEN
SELECT NEEDED_FIELD INTO otherVar WHERE condition;
....
-ou-
SELECT NEEDED_FIELD INTO var WHERE condition;
EXCEPTION
WHEN NO_DATA_FOUND
....
Le second cas me semble un peu plus élégant, car je peux alors utiliser NEEDED_FIELD, que j’aurais dû sélectionner dans la première instruction après la condition dans le premier cas. Moins de code. Mais si la procédure stockée fonctionne plus rapidement avec COUNT (*), cela ne me dérange pas de taper un peu plus pour compenser la vitesse de traitement.
Des allusions? Est-ce que je manque une autre possibilité?
MODIFIER J'aurais dû mentionner que tout cela est déjà imbriqué dans un FOR LOOP. Je ne sais pas si cela fait une différence avec l’utilisation d’un curseur, car je ne pense pas pouvoir déclarer le curseur comme une sélection dans la boucle For.
Je ne voudrais pas utiliser un curseur explicite pour le faire. Steve F. déconseille aux utilisateurs d'utiliser des curseurs explicites lorsqu'un curseur implicite peut être utilisé.
La méthode avec count(*)
n'est pas sûre. Si une autre session supprime la ligne qui remplit la condition après la ligne avec count(*)
et avant la ligne avec select ... into
, le code lève une exception qui ne sera pas traitée.
La deuxième version de l'article d'origine n'a pas ce problème, et il est généralement préféré.
Cela dit, il existe une surcharge mineure avec l'exception, et si vous êtes sûr à 100% que les données ne changeront pas, vous pouvez utiliser la fonction count(*)
, mais je le déconseille.
J'ai exécuté ces tests de performance sur Oracle 10.2.0.1 sur 32 bits Windows. Je ne regarde que le temps écoulé. Il existe d'autres faisceaux de test pouvant donner plus de détails (tels que le nombre de verrous et la mémoire utilisée).
SQL>create table t (NEEDED_FIELD number, COND number);
Table créée.
SQL>insert into t (NEEDED_FIELD, cond) values (1, 0);
1 rangée créée.
declare
otherVar number;
cnt number;
begin
for i in 1 .. 50000 loop
select count(*) into cnt from t where cond = 1;
if (cnt = 1) then
select NEEDED_FIELD INTO otherVar from t where cond = 1;
else
otherVar := 0;
end if;
end loop;
end;
/
Procédure PL/SQL terminée avec succès.
Écoulé: 00: 00: 02.70
declare
otherVar number;
begin
for i in 1 .. 50000 loop
begin
select NEEDED_FIELD INTO otherVar from t where cond = 1;
exception
when no_data_found then
otherVar := 0;
end;
end loop;
end;
/
Procédure PL/SQL terminée avec succès.
Écoulé: 00: 00: 03.06
SELECT INTO supposant qu'une seule ligne sera renvoyée, vous pouvez utiliser une instruction de la forme suivante:
SELECT MAX(column)
INTO var
FROM table
WHERE conditions;
IF var IS NOT NULL
THEN ...
Le SELECT vous donnera la valeur s'il en existe une et la valeur NULL au lieu d'une exception NO_DATA_FOUND. La surcharge introduite par MAX () sera minimale à zéro car le jeu de résultats contient une seule ligne. Elle présente également l’avantage d’être compacte par rapport à une solution basée sur un curseur et de ne pas être vulnérable aux problèmes de concurrence tels que la solution en deux étapes de la publication originale.
Une alternative au code de @ Steve.
DECLARE
CURSOR foo_cur IS
SELECT NEEDED_FIELD WHERE condition ;
BEGIN
FOR foo_rec IN foo_cur LOOP
...
END LOOP;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END ;
La boucle n'est pas exécutée s'il n'y a pas de données. Les boucles Cursor FOR sont la solution: elles permettent d’éviter beaucoup de tâches ménagères. Une solution encore plus compacte:
DECLARE
BEGIN
FOR foo_rec IN (SELECT NEEDED_FIELD WHERE condition) LOOP
...
END LOOP;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END ;
Ce qui fonctionne si vous connaissez l’instruction select complète au moment de la compilation.
@DCookie
Je veux juste souligner que vous pouvez laisser de côté les lignes qui disent
EXCEPTION
WHEN OTHERS THEN
RAISE;
Vous obtiendrez le même effet si vous laissez tout le bloc d'exception ensemble, et le numéro de ligne indiqué pour l'exception sera la ligne où l'exception est réellement levée, et non la ligne du bloc d'exceptions où elle a été relevée.
Stephen Darlington fait valoir un très bon point, et vous pouvez voir que si vous modifiez mon critère de référence pour utiliser un tableau de taille plus réaliste si je remplis le tableau sur 10 000 lignes en utilisant les éléments suivants:
begin
for i in 2 .. 10000 loop
insert into t (NEEDED_FIELD, cond) values (i, 10);
end loop;
end;
Puis relancez les repères. (J'ai dû réduire le nombre de boucles à 5000 pour obtenir des temps raisonnables).
declare
otherVar number;
cnt number;
begin
for i in 1 .. 5000 loop
select count(*) into cnt from t where cond = 0;
if (cnt = 1) then
select NEEDED_FIELD INTO otherVar from t where cond = 0;
else
otherVar := 0;
end if;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:04.34
declare
otherVar number;
begin
for i in 1 .. 5000 loop
begin
select NEEDED_FIELD INTO otherVar from t where cond = 0;
exception
when no_data_found then
otherVar := 0;
end;
end loop;
end;
/
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.10
La méthode à l'exception est maintenant plus de deux fois plus rapide. Donc, dans presque tous les cas, la méthode:
SELECT NEEDED_FIELD INTO var WHERE condition;
EXCEPTION
WHEN NO_DATA_FOUND....
est le chemin à parcourir. Il donnera des résultats corrects et est généralement le plus rapide.
Si c'est important, vous devez vraiment comparer les deux options!
Cela dit, j’ai toujours utilisé la méthode des exceptions, le raisonnement étant qu’il était préférable de ne toucher la base de données qu’une fois.
Oui, il vous manque des curseurs
DECLARE
CURSOR foo_cur IS
SELECT NEEDED_FIELD WHERE condition ;
BEGIN
OPEN foo_cur;
FETCH foo_cur INTO foo_rec;
IF foo_cur%FOUND THEN
...
END IF;
CLOSE foo_cur;
EXCEPTION
WHEN OTHERS THEN
CLOSE foo_cur;
RAISE;
END ;
certes, il s’agit de davantage de code, mais il n’utilise pas EXCEPTION comme contrôle de flux et, après avoir appris la plupart de mes PL/SQL grâce au livre de programmation PL/SQL de Steve Feuerstein, j’estime être une bonne chose.
Que ce soit plus rapide ou pas, je ne sais pas (je fais très peu de PL/SQL de nos jours).
Plutôt que d'avoir des boucles de curseur imbriquées, une approche plus efficace consisterait à utiliser une boucle de curseur avec une jointure externe entre les tables.
BEGIN
FOR rec IN (SELECT a.needed_field,b.other_field
FROM table1 a
LEFT OUTER JOIN table2 b
ON a.needed_field = b.condition_field
WHERE a.column = ???)
LOOP
IF rec.other_field IS NOT NULL THEN
-- whatever processing needs to be done to other_field
END IF;
END LOOP;
END;
La première (excellente) réponse a déclaré -
La méthode avec count () n'est pas sûre. Si une autre session supprime la ligne qui remplit la condition après la ligne avec le compte (*) et avant la ligne avec le choix ... dans, le code lève une exception qui ne sera pas traitée.
Pas si. Au sein d'une unité de travail logique, Oracle est totalement cohérent. Même si une personne valide la suppression de la ligne entre un compte et une sélection Oracle, pour la session active, obtient les données des journaux. Si ce n'est pas le cas, vous obtiendrez une erreur "instantané trop ancien".
vous ne devez pas utiliser open lorsque vous utilisez des boucles for.
declare
cursor cur_name is select * from emp;
begin
for cur_rec in cur_name Loop
dbms_output.put_line(cur_rec.ename);
end loop;
End ;
ou
declare
cursor cur_name is select * from emp;
cur_rec emp%rowtype;
begin
Open cur_name;
Loop
Fetch cur_name into Cur_rec;
Exit when cur_name%notfound;
dbms_output.put_line(cur_rec.ename);
end loop;
Close cur_name;
End ;
Peut-être bat-on un cheval mort ici, mais j'ai jalonné le curseur pour la boucle, et cela a fonctionné aussi bien que la méthode no_data_found
declare
otherVar number;
begin
for i in 1 .. 5000 loop
begin
for foo_rec in (select NEEDED_FIELD from t where cond = 0) loop
otherVar := foo_rec.NEEDED_FIELD;
end loop;
otherVar := 0;
end;
end loop;
end;
Procédure PL/SQL terminée avec succès.
Écoulé: 00: 00: 02.18
Le décompte (*) ne déclenchera jamais d'exception car il renvoie toujours le décompte réel ou 0 - zéro, quoi qu'il arrive. J'utiliserais le compte.