-
Notifications
You must be signed in to change notification settings - Fork 1
SQL et PL SQL
Note: cela concerne principalement Oracle 11g (et ultérieur).
Utilisation de with (CTE)
Il est possible d'utiliser with pour calculer des tables temporaires pour découper une grosse requête:
with
a as (
-- requête compliquée
),
b as (
select *
from a
inner join x on a.id = x.id
)
select *
from b
Lorsqu'on utilise un ROWTYPE de cette façon:
procedure foobar(id in SOME_TABLE.ID%type, data out SOME_TABLE%ROWTYPE) is
cursor cur_st is select * from SOME_TABLE where ID = id;
begin
open cur_st;
fetch cur_st into data;
close cur_st;
exception
when others then
if cur_st%isopen then close cur_st; end if;
end;
Alors si la procédure ne trouve pas de lignes, toutes les colonnes seront NULL.
A privilégier en toute circonstance :
for cur in (select * from some_table) loop
dbms_output.put_line(to_char(cur.id));
end loop;
Ce qui gère automatiquement l'ouverture, le fetch et la clôture du curseur. C'est également plus lisible car cela crée des variables locales à la boucle.
En ne chargeant les éléments qu'unitairement :
declare
t_row employees%ROWTYPE;
cursor cur1(P_AGE employees.age%TYPE) is
select *
from employees
where age > P_AGE
;
begin
open cur1(18);
loop
fetch cur1 collect into t_row;
exit when cur1%notfound;
dbms_output.put_line('age: ' || t_row.age);
end loop;
close cur1;
end;
Le exit when permet de sortir de la boucle en cas d'événements aucune donnée trouvée.
En ne chargeant les éléments qu'unitairement :
declare
type type_employees is table of employees%ROWTYPE;
t_rows type_employees;
cursor cur1 is
select *
from employees
where age > P_AGE
;
begin
open cur1;
loop
fetch cur1 bulk collect into t_rows limit 100;
for i in 1 .. t_rows.count loop
dbms_output.put_line('age: ' || t_rows(i).age);
end loop;
exit when cur1%notfound;
end loop;
close cur1;
end;
Il faut laisser exit when après la boucle sur les éléments car sinon on va ignorer le dernier bloc (si on a
130 résultats, on aura 2 blocs : un de 100, l'autre de 30).
Il ne faut pas utiliser first et last comme ici :
forall i in t_rows.first..t_rows.last
update employees set salary = salary * 1.25 where id = t_rows(i).id;
Dans ce cas Oracle gère le fait que first et last soient null alors que si on s'en sert dans une boucle for,
le résultat est une erreur de conversion "numérique":
for i in t_rows.first .. t_rows.last loop -- echec: i in NULL .. NULL => non convertible en number.
dbms_output.put_line('age: ' || t_rows(i).age);
end loop;
Il vaut mieux gérer l'exception NO_DATA_FOUND que de faire un count(*) au préalable.
Voir également Predefined PL/SQL Exceptions
begin
select * into t_row from employees where id = 1;
exception
when NO_DATA_FOUND then
RAISE_APPLICATION_ERROR(-20001, 'employee not found: ' || id);
raise; -- pour que le compilateur PL/SQL comprenne qu'on quitte; ce n'est nullement obligatoire.
-- when TOO_MANY_ROWS then
-- raise;
end;
Cela vaut également mieux qu'un curseur explicite (moins lourd à déclarer).
Un indéniable avantage est la lisibilité au niveau de l'intention qu'un count(*).
Éviter ceci:
begin
select * into t_row from employees where id = 1;
exception
when others then
raise; -- relance l'exception catchée.
end;
Dans ce cas, on perds la stacktrace de l'exception.
On peut utiliser dbms_utility.format_error_backtrace pour la loguer.
Voir: FORMAT_CALL_STACK
Si vous trouvez une erreur, un lien invalide, n'hésitez pas à créer un ticket: https://github.com/glhez/docs/issues