-
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