Stored procedures
Mis Ă jour
Mis Ă jour
Nous pouvons pré-coder des fonctions directement dans la base de données, qui se compose de plusieurs commandes SQL, ainsi que des transactions.
Quels avantages ?
La logique business est codée à un point central de la plateforme
Moins dâerreurs
Maintenance plus facile
EmpĂȘcher de lâaccĂšs direct aux tables par les utilisateurs et/ou APIS
Abstraire de la complexité
ContrĂŽle dâaccĂšs et permissions
Imaginons le transfert dâargent dans une plateforme bancaire. La sĂ©quence de requĂȘtes se trouve oĂč ?
On peut, bien sûr, implémenter la logique dans le client :
En revanche, ceci est une trÚs mauvaise idée :
Duplication X fois de la logique (pour chaque type de client qui existe) Si le rĂšglement bancaire change ?
Il ne faut jamais faire confiance aux clients !
Logiquement, la plupart de la logique se trouvent plutĂŽt en interne, dans nos APIs :
C'est plus sĂ©curisĂ©, mais, on a potentiellement les mĂȘmes problĂšmes de maintenance. Il y a une duplication de la logique sur les diffĂ©rents APIs. Une solution potentielle est d'avoir un API monolithique, ou bien une librairie partagĂ©e entre toutes les implĂ©mentations.
Sinon, on peut héberger le logique business juste à cÎté des données :
Malgré l'API utilisé, la séquence nécessaire pour une opération reste inchangé. Et en plus, ni l'API ni le client voit les détails de l'opération.
Nous allons créer une procédure stockée qui agit sur plusieurs tables pour affecter un transfert d'argent. Voici le DDL pour les deux tables :
create table customer (
customer_id int not null auto_increment,
name varchar(256),
surname varchar(256),
balance decimal(10,2) not null default(0),
primary key (customer_id)
);
create table journal (
journal_id int not null auto_increment,
customer_id int not null,
amount decimal(10,2) not null default(0),
date timestamp not null,
primary key (journal_id),
foreign key (customer_id) references customer(customer_id)
);
insert into customer (name, surname, balance) values ("Kevin", "Glass", 5000);
insert into customer (name, surname, balance) values ("Pierre", "Laurendeau", 7000);
insert into customer (name, surname, balance) values ("Florence", "Perrousset", 3500);
On peut rĂ©diger une fonction (en tant que stored procedure) qui peut ĂȘtre invoquĂ© Ă tout moment, avec des paramĂštres, pour effectuer un transfert d'argent :
DELIMITER //
create procedure Transfer (amount decimal(10,2), from_id int, to_id int) modifies sql data
begin
declare exit handler for sqlexception
begin
rollback;
resignal;
end;
start transaction;
select @soldeFrom:=balance from customer where customer_id=from_id for update;
select @soldeTo:=balance from customer where customer_id=to_id for update;
set @soldeTo:=@soldeTo + amount;
set @soldeFrom:=@soldeFrom - amount;
if @soldeFrom < 0 then
signal sqlstate '45000' set message_text="Not enough available for transfer.";
end if;
update customer set balance = @soldeTo where customer_id=to_id;
update customer set balance = @soldeFrom where customer_id=from_id;
set @ts = CURRENT_TIMESTAMP();
insert into journal (customer_id, amount, date) values (to_id, amount, @ts);
insert into journal (customer_id, amount, date) values (from_id, -amount, @ts);
commit;
end;
//
DELIMITER ;
On commence notre procédure stockée par :
DELIMITER //
En fait, ici, on change le symbole reconnu par l'interprĂšte SQL pour signaler la fin d'une commande. On le fait parce qu'on veut empĂȘcher l'interprĂšte d'interprĂ©ter les commandes qui se trouvent Ă l'intĂ©rieur de notre procĂ©dure qu'on est en train de rĂ©diger !
Pour signaler la fin de déclaration de notre procédure, on évoque le délimiteur :
//
Une fois terminé, on rétablit le délimiteur :
DELIMITER ;
La procédure se crée avec une syntaxe précis :
create procedure NOM ( paramÚtres séparés par des virgules ) modifies sql data
begin
/** Les instructions de la procédure **/
end;
AprÚs la déclaration d'une procédure, on peut l'invoquer à tout moment avec :
call NOM(... paramĂštres ...);
Il faut toujours penser aux erreurs qui peuvent arriver. Nous commençons donc par préciser que faire en cas d'erreur :
declare exit handler for sqlexception
begin
rollback;
resignal;
end;
Dans cet exemple, on précise que dans le cas d'un sqlexception
, on fait d'abord un rollback
, puis on renvoie l'erreur plus haut dans le tas d'appels (avec resignal
).
Comment signaler manuellement une erreur ? Avec la commande signal
:
if @soldeFrom < 0 then
signal sqlstate '45000' set message_text="Not enough available for transfer.";
end if;
Le state 45000
est réservé aux erreurs personnalisées.
Nous commençons avec les données suivantes :
1
Kevin
Glass
5000.00
2
Pierre
Laurendeau
7000.00
3
Florence
Perrousset
3500.00
On peut utiliser notre procédure afin d'effectuer un transfert de 2000 ⏠de Kevin à Pierre :
call Transfer(2000, 1, 2);
1
Kevin
Glass
3000.00
2
Pierre
Laurendeau
9000.00
3
Florence
Perrousset
3500.00
Tout s'est bien passé !
Essayons avec une erreur :
call Transfer(5000, 1, 2);
ERROR 1644 (45000): Not enough available for transfer.
On peut récupérer la liste de procédures pour notre base de données (on filtre par base de données, car il y en a déjà d'autres qui existent dans le SGBDR) :
show procedure status where db = 'bank';
On peut récupérer le listing d'une procédure existante :
show create procedure Transfer;
On ne peut pas modifier directement les procédures. On est obligé de les supprimer et les remplacer avec une autre version.
drop procedure [nom];
Certains clients comme MySQL Workbench ont une interface qui facilite ces opérations.
Vous ĂȘtes Ă©diteur dâune solution de publicitĂ© en ligne qui va rĂ©compenser lâinternaute de lâattention passĂ©e sur les pubs (exemple, le navigateur Brave).
Au cĆur de votre solution est la logique business suivante :
Vous avez une base dâĂ©diteurs partenaires (journaux, blogs, mĂ©dia, etc.). Chaque Ă©diteur classe leurs contenus par une ou plusieurs catĂ©gories : sport, actualitĂ©, divertissement, science, politique, enfant
Un annonceur de pub prĂ©paye un budget total pour une publicitĂ©. Pour chaque pub, lâannonceur prĂ©cise le prix-par-vu quâil est prĂȘt Ă payer par chaque catĂ©gorie cible. Plus le prix est Ă©levĂ©, plus la pub aura la chance dâapparaĂźtre dans un site Ă©diteur de ce genre.
Quand la pub apparaĂźt sur le site dâun Ă©diteur de contenu (journal, blog, etc.), le prix (correspondant Ă la catĂ©gorie) est dĂ©duit du budget total de lâannonceur :
75% est allouĂ© au compte de lâĂ©diteur
25% est allouĂ© au compte de lâinternaute
CrĂ©ez le schĂ©ma pour votre solution, ainsi quâune procĂ©dure SQL qui effectue le transfert de valeur au moment dâune vue sur une page web dâun Ă©diteur.
Votre procĂ©dure doit ĂȘtre tolĂ©rante aux fautes ainsi que des requĂȘtes concurrentes !
Attention : lâannonceur ne peut jamais avoir un solde nĂ©gatif.
Chaque partie prenante doit ĂȘtre capable de rĂ©cupĂ©rer son historique : ex. Lâannonceur veut voir la rĂ©partition de dĂ©pense sur les Ă©diteurs, lâĂ©diteur veut voir la rĂ©partition des sources de revenu, lâinternaute veut voir lâhistorique de pubs dĂ©jĂ vues.