- Moi : "Salut, ça te dérange si je crée des JOB sur la base de test ? Ca n'ira jamais en prod"
- Mon DBA : "Bah pas de problème, je t'ai attribué le rôle DBA, fais toi plais'. Cela dit, si c'est pour des rapports XXXX, ils sont déjà déposés en automatique [...je vous passe les détails]"
- Moi : "Non, non, c'est pour Rémy, qui m'a demandé de lui rappeler tous les jours qu'il doit commander le repas"
- Mon DBA : "Euh... ouais. Dans ce cas, j'ai une 11GR2 de test pour préparer la migration, je te crée un compte dessus"
Bref, je me retrouve avec une 11gR2 et je vais faire joujou avec dbms_schudeler et utl_mail.
1) La structure
Bon, histoire de faire un peu de paramétrage sans faire d'usine à gaz non plus, on se limite :
Aux données personnes :
PACMANN>CREATE TABLE personne (
2 id_personne NUMBER NOT NULL PRIMARY KEY,
3 nom VARCHAR2(50) NOT NULL,
4 prenom VARCHAR2(50) NOT NULL,
5 email VARCHAR2(50));
Table created.
Aux notifications, identifiées fonctionnellement par un code
PACMANN>CREATE TABLE message (
2 id_message NUMBER NOT NULL PRIMARY KEY,
3 objet VARCHAR2(100) NOT NULL,
4 corps VARCHAR2(4000),
5 code_message VARCHAR2(50) NOT NULL UNIQUE);
Table created.
Une table de lien pour abonner les gens aux notifications :
PACMANN>CREATE TABLE message_destinataire (
2 id_message NUMBER NOT NULL,
3 id_personne NUMBER NOT NULL,
4 CONSTRAINT pk_mess_dest PRIMARY KEY (id_message, id_personne),
5 CONSTRAINT fk_md_message FOREIGN KEY (id_message) REFERENCES message(id_message),
6 CONSTRAINT fk_md_personne FOREIGN KEY (id_personne) REFERENCES personne(id_personne));
Table created.
2) Les données
J'insère Rémy et moi. Anonymisés bien sûr dans le post de blog.
On note aussi que la syntaxe INSERT ALL est tout à fait superflue ici, l'esprit étant d'insérer dans plusieurs fois (potentiellement dans des tables différentes) à partir d'une même source.
(Ici dual, puisque comme dit, il s'agit juste d'épater les newbs)
PACMANN>INSERT ALL
2 INTO personne VALUES(1, 'PACMANN', 'Pacmann', 'yyyy@yyyy.com')
3 INTO personne VALUES(2, 'ANONYMOUS', 'Rémy', 'xxxx@xxxx.com')
4 SELECT * FROM dual;
2 rows created.
PACMANN>commit;
Commit complete.
Dans le message, on voit apparaître une variable $1, qui permettra ensuite de personnaliser les messages à l'exécution.
PACMANN>INSERT INTO message
2 VALUES (1, 'Notification repas',
3 'N''oublies pas de commander ton repas de midi, $1 !',
4 'NOTIF_REPAS_MIDI');
1 row created.
PACMANN>commit;
Commit complete.
PACMANN>INSERT INTO message_destinataire
2 SELECT id_message, id_personne
3 FROM message
4 CROSS JOIN personne;
2 rows created.
PACMANN>commit;
Commit complete.
3) Le package d'envoi des mails :
Dans le package de notification, la procédure send_notif_perso envoie une notification à tous les abonnés.
Comme dit, il suffit d'inclure des $1 et $2 pour personnaliser les messages avec les noms et prénoms des abonnés.
J'avais hésité à faire un envoi global à tous les abonnés, ce qui m'aurait permis, pour la première fois de ma vie, d'utiliser listagg pour constituer la chaîne aggrégée des destinataires... tant pis
(C'est pour cela que la procédure s'appelle send_notif_perso, ça appelle un send_notif_global)
PACMANN>CREATE OR REPLACE PACKAGE pkg_notif AS
2 PROCEDURE send_notif_perso(p_code_message IN VARCHAR2);
3 END pkg_notif;
4 /
Package created.
PACMANN>CREATE OR REPLACE PACKAGE BODY pkg_notif
2 AS
3
4 PROCEDURE send_notif_perso(p_code_message IN VARCHAR2) IS
5
6 BEGIN
7 FOR crow IN (
8 SELECT c.nom, c.prenom, c.email, a.objet, a.corps
9 FROM message a
10 INNER JOIN message_destinataire b on a.id_message = b.id_message
11 INNER JOIN personne c on b.id_personne = c.id_personne
12 WHERE a.code_message = p_code_message)
13 LOOP
14
15 UTL_MAIL.SEND (
16 sender => 'Mr_Notif',
17 recipients => crow.email,
18 subject => crow.objet,
19 message => replace(replace(crow.corps, '$1', crow.prenom), '$2', crow.nom));
20
21 END LOOP;
22 END send_notif_perso;
23
24 END pkg_notif;
25 /
Package body created.
4) La planification
Puis on enpaquette tout ça dans un job, qu'on programme du lundi au vendredi à 10h30, sachant que les commandes se ferment à 11h.
PACMANN>BEGIN
2 DBMS_SCHEDULER.create_job (
3 job_name => 'notification_repas_midi',
4 job_type => 'PLSQL_BLOCK',
5 job_action => 'BEGIN pkg_notif.send_notif_perso(''NOTIF_REPAS_MIDI''); END;',
6 start_date => sysdate,
7 repeat_interval => 'freq=weekly;byday=mon,tue,wed,thu,fri;byhour=10;byminute=30',
8 end_date => NULL,
9 enabled => TRUE,
10 comments => 'La notif quotidienne pour ne pas oublier de commander le repas de midi !');
11 END;
12 /
PL/SQL procedure successfully completed.
Un petit lancement manuel du job pour vérifier que ça marche :
PACMANN>exec DBMS_SCHEDULER.RUN_JOB('notification_repas_midi');
PL/SQL procedure successfully completed.
Il n'aura plus d'excuses pour mourir de faim, Rémy. Enfin si, si le job est shooté, si la base joujou est dézinguée, ... bref, si ça arrive, cela sera clairement son destin.
5) Quelques compléments
J'ai un peu lutté sur quelques points :
- UTL_MAIL non installé sur la base. En sys :
@$ORACLE_HOME/rdbms/admin/utlmail.sql
@$ORACLE_HOME/rdbms/admin/prvtmail.plb
alter system set smtp_out_server = ton_serveur scope=both
- A partir de 11g, les utilitaires du type UTL_MAIL sont restreint par des ACL (access control list), j'ai bidouillé par l'intermédiaire de ce site :
http://www.oracleflash.com/36/Oracle-11g-Access-Control-List-for-External-Network-Services.html
(A noter que la première fois, j'ai connement exécuté les exemples sans remplacer par le nom de mon smtp... erghl)
- Comme je manquais de patience avec la doc oracle, je suis allé chercher les exemples de définitions de planifications chez Donald (ne le dites pas trop fort, ma fille va vouloir y aller aussi)
http://www.dba-oracle.com/t_dbms_scheduler_examples.htm
- Comme j'ai des goûts de chiotte, je change régulièrement de mise en forme pour les requêtes que je poste ici. Vous pouvez laisser un message pour me dire que j'ai vraiment des goûts de chiotte.