Project Oxygen & Ideo-LabIDEO LAB Dashboard 2026

🟩 PL/SQL — Guide complet (Oracle)

Procédures, fonctions, packages, triggers, exceptions, SQL dynamique, perf & sécurité. Idéal pour DBA / dev back-end / data apps Oracle.

PL/SQL = langage procĂ©dural d’Oracle pour Ă©crire des blocs BEGIN...END exĂ©cutĂ©s cĂŽtĂ© base (logique, rĂšgles mĂ©tier, batch, APIs). Ses forces : performances (proximitĂ© donnĂ©es), transactions, robustesse (exceptions), packages (encapsulation). Attention : couplage, SQL dynamique mal gĂ©rĂ©, et performances si code row-by-row.
1.1

Vue d’ensemble

Pourquoi PL/SQL, oĂč l’utiliser, architecture, modĂšles d’exĂ©cution.

OracleTransactionsAPI DB
1.2

Blocs & structure

DECLARE/BEGIN/EXCEPTION/END, variables, types, scope, NULL.

BlockTypesExceptions
2.1

Procédures & fonctions

IN/OUT/IN OUT, RETURN, DETERMINISTIC, pipelined (aperçu).

ProcedureFunctionParams
2.2

Cursors & boucles

FOR loops, bulk collect, forall, éviter le row-by-row.

CursorBulkFORALL
3.1

Packages (API)

Spec vs Body, encapsulation, variables de package, init section.

PackageSpec/BodyEncapsulation
3.2

Triggers

BEFORE/AFTER, row vs statement, audit, piĂšges & anti-patterns.

TriggerAuditRisques
4.1

SQL dynamique

EXECUTE IMMEDIATE, bind variables, DBMS_SQL, injection.

Dynamic SQLInjectionBind
4.2

Exceptions & erreurs

RAISE, RAISE_APPLICATION_ERROR, log, stratĂ©gie d’erreurs.

ExceptionLogErrors
5.1

Performance

Bulk, context switches, instrumentation, plan SQL, tuning.

PerfExplainStats
5.2

Sécurité & droits

Definer vs invoker, grants, roles, DBMS_ASSERT, least privilege.

SecurityAUTHIDGrants
6.1

Tests & qualité

utPLSQL, jeux de données, transactions, mocks, CI (pistes).

utPLSQLCIQA
6.2

Outils & workflow

SQL Developer, SQLcl, Liquibase/Flyway, code style, Git.

SQLclMigrationsGit
1.1 — PL/SQL : rîle, usages, architecture (mental model)
Pourquoi PL/SQL existe

Oracle a ajouté PL/SQL pour exprimer des rÚgles métier et des traitements au plus prÚs des données : validations, batch, orchestration, APIs internes, manipulations de gros volumes, et gestion transactionnelle robuste.

ForcesImpactExemples
ProximitĂ© donnĂ©esmoins d’allers-retours app ↔ DBbatch nightly, calculs, consolidation
TransactionsCOMMIT/ROLLBACK maßtrisésprocess multi-tables cohérents
EncapsulationAPI DB (packages) stablepkg_customer.create_customer()
Exceptionserreurs gérées + loggingRAISE_APPLICATION_ERROR, journaux
Phrase clé : PL/SQL doit servir à produire un résultat cÎté DB (perf/consistance), pas à réinventer une application complÚte dans la base.
OĂč l’utiliser (cas “bons”)
  • APIs DB : packages exposant des procĂ©dures/fonctions stables (contrat).
  • Batch / ETL : traitement volumĂ©trique, transformations, agrĂ©gations.
  • Garanties mĂ©tier : validations impossibles cĂŽtĂ© app seule (cohĂ©rence multi-tables).
  • Audit : journalisation centralisĂ©e (attention aux triggers lourds).
  • Ops DBA : scripts maintenance, purge, reindex, job scheduler.
OĂč Ă©viter (souvent)
  • Logique UI/HTTP/JSON trop “application” (mieux cĂŽtĂ© service back).
  • Triggers complexes en cascade (difficile Ă  dĂ©boguer & perf).
  • SQL dynamique sans bind variables (risque injection + perf).
ModĂšle d’exĂ©cution (simple)
Client (app) → Oracle SQL Engine → (si besoin) PL/SQL Engine → SQL Engine → rĂ©sultat Points clĂ©s: - Le SQL est exĂ©cutĂ© par le SQL Engine - Le PL/SQL orchestre (boucles, conditions, exceptions) - Les allers/retours PL/SQL↔SQL ("context switches") coĂ»tent cher => prĂ©fĂ©rer BULK/FORALL
IdĂ©e : Ă©crire PL/SQL qui pousse le maximum de travail “en set-based SQL”, puis PL/SQL pour orchestrer / sĂ©curiser / logger.
Anti-patterns fréquents
Anti-patternPourquoi c’est mauvaisAlternative
Row-by-row (cursor loop sur millions)lent, context switchesSQL set-based + BULK COLLECT / FORALL
Triggers qui font “tout”debug difficile, perf alĂ©atoirepackages + contraintes + audit ciblĂ©
EXECUTE IMMEDIATE concatinjection + hard parsebind variables + DBMS_ASSERT si nécessaire
1.2 — Blocs PL/SQL : DECLARE / BEGIN / EXCEPTION / END
Structure canonique
DECLARE
  -- Déclarations (variables, constantes, types)
  v_customer_id  NUMBER;
  v_name         VARCHAR2(200);
BEGIN
  -- Logique
  SELECT customer_id, name
    INTO v_customer_id, v_name
    FROM customers
   WHERE email = 'a@b.com';

  DBMS_OUTPUT.PUT_LINE('ID='||v_customer_id||' name='||v_name);

EXCEPTION
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('No customer');
  WHEN TOO_MANY_ROWS THEN
    DBMS_OUTPUT.PUT_LINE('Email not unique!');
  WHEN OTHERS THEN
    -- à traiter proprement (log + re-raise si nécessaire)
    DBMS_OUTPUT.PUT_LINE('Unexpected: '||SQLERRM);
END;
/
Types & variables (rappel)
TypeUsageTips
NUMBERnumériquesprécision/scale si besoin
VARCHAR2textetaille suffisante, éviter CHAR sauf cas
DATE / TIMESTAMPtempspréférer TIMESTAMP si précision
%TYPE / %ROWTYPEtypages “dynamiques”rĂ©duit mismatch avec schĂ©ma
Scope & bonnes pratiques
  • %TYPE pour coller au schĂ©ma (moins d’erreurs lors des Ă©volutions).
  • NULL = valeur “inconnue” : attention aux comparaisons (utiliser IS NULL).
  • PrĂ©fĂ©rer constants et noms explicites (prĂ©fixes v_/p_/g_).
  • Éviter DBMS_OUTPUT en prod → instrumentation propre (table log / APEX_DEBUG / etc.).
Truc pro : variables ancrées
v_salary employees.salary%TYPE;
v_emp    employees%ROWTYPE;
Tip : si ton code évolue beaucoup, ancrer les types au schéma réduit énormément les régressions.
2.1 — ProcĂ©dures & fonctions : contrats, paramĂštres, patterns
Procédure

Une procĂ©dure exĂ©cute une action (insert/update/process) ; elle ne “retourne” pas directement une valeur comme une fonction, mais peut utiliser OUT/IN OUT.

CREATE OR REPLACE PROCEDURE p_create_customer(
  p_email IN  VARCHAR2,
  p_name  IN  VARCHAR2,
  p_id    OUT NUMBER
) AS
BEGIN
  INSERT INTO customers(email, name) VALUES(p_email, p_name)
  RETURNING customer_id INTO p_id;
END;
/
Fonction

Une fonction retourne une valeur. Elle est souvent utilisée dans SQL (avec prudence) ou dans du PL/SQL.

CREATE OR REPLACE FUNCTION f_customer_status(p_id IN NUMBER)
RETURN VARCHAR2
AS
  v_status VARCHAR2(20);
BEGIN
  SELECT status INTO v_status FROM customers WHERE customer_id = p_id;
  RETURN v_status;
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    RETURN 'UNKNOWN';
END;
/
Prudence : les fonctions appelĂ©es “dans SQL” peuvent impacter perf et gĂ©nĂ©rer des surprises (purity, context).
IN / OUT / IN OUT
ModeRîleÀ utiliser quand
INentrĂ©e (read-only)paramĂštre “normal”
OUTsortierenvoyer un id / statut / résultats
IN OUTentrĂ©e + sortierare ; Ă©viter si API peut ĂȘtre plus claire
-- Nommer clairement:
p_customer_id IN NUMBER
o_status      OUT VARCHAR2
Patterns d’API PL/SQL (pro)
PatternIdéeExemple
CRUD API packageexposer des opérations stablespkg_customer.create/update/delete
Validation centraliséeune seule source de véritépkg_rules.validate_order()
Batch orchestratorcoordonne étapes + commitspkg_batch.run(p_date)
2.2 — Cursors, boucles & bulk processing (Ă©viter le row-by-row)
Cursor FOR loop (simple)
FOR r IN (SELECT customer_id FROM customers WHERE status='ACTIVE') LOOP
  -- traitement
  NULL;
END LOOP;
Bulk Collect + FORALL (pro perf)
DECLARE
  TYPE t_ids IS TABLE OF customers.customer_id%TYPE;
  v_ids t_ids;
BEGIN
  SELECT customer_id BULK COLLECT INTO v_ids
  FROM customers
  WHERE status='ACTIVE';

  FORALL i IN 1..v_ids.COUNT
    UPDATE customers SET last_seen = SYSTIMESTAMP
    WHERE customer_id = v_ids(i);
END;
/
Pourquoi c’est rapide : FORALL rĂ©duit les context switches PL/SQL↔SQL et pousse les opĂ©rations en “set”.
RĂšgles de perf
  • PrĂ©fĂ©rer SQL set-based (UPDATE/INSERT MERGE) plutĂŽt que boucles.
  • Si boucle obligatoire : BULK COLLECT (par batch) + FORALL.
  • Utiliser un LIMIT sur bulk collect en trĂšs gros volumes.
  • Mesurer : instrumentation + stats + plans SQL (sinon “optimisations” inutiles).
Bulk par batches
LOOP
  FETCH c BULK COLLECT INTO v_rows LIMIT 1000;
  EXIT WHEN v_rows.COUNT = 0;

  FORALL i IN 1..v_rows.COUNT
    -- DML
END LOOP;
Astuce : choisir LIMIT (ex 500/1000/5000) selon mémoire/redo/perf.
3.1 — Packages : Spec vs Body, encapsulation, design d’API
Package Spec (contrat public)
CREATE OR REPLACE PACKAGE pkg_customer AS
  PROCEDURE create_customer(p_email IN VARCHAR2, p_name IN VARCHAR2, o_id OUT NUMBER);
  FUNCTION  get_status(p_id IN NUMBER) RETURN VARCHAR2;

  -- Constantes publiques (si utile)
  c_status_active CONSTANT VARCHAR2(10) := 'ACTIVE';
END pkg_customer;
/
Package Body (implémentation)
CREATE OR REPLACE PACKAGE BODY pkg_customer AS

  -- Private helper (non exposé)
  FUNCTION normalize_email(p_email IN VARCHAR2) RETURN VARCHAR2 IS
  BEGIN
    RETURN LOWER(TRIM(p_email));
  END;

  PROCEDURE create_customer(p_email IN VARCHAR2, p_name IN VARCHAR2, o_id OUT NUMBER) AS
  BEGIN
    INSERT INTO customers(email, name)
    VALUES(normalize_email(p_email), p_name)
    RETURNING customer_id INTO o_id;
  END;

  FUNCTION get_status(p_id IN NUMBER) RETURN VARCHAR2 AS
    v_status customers.status%TYPE;
  BEGIN
    SELECT status INTO v_status FROM customers WHERE customer_id = p_id;
    RETURN v_status;
  EXCEPTION
    WHEN NO_DATA_FOUND THEN RETURN 'UNKNOWN';
  END;

BEGIN
  -- Section d'initialisation du package (optionnelle)
  NULL;
END pkg_customer;
/
Pourquoi les packages sont “la brique pro”
  • Encapsulation : tu exposes une API stable, tu caches les dĂ©tails.
  • Contrat : la spec sert de documentation vivante.
  • Organisation : regrouper par domaine (customer, billing, batch, security).
  • Versioning : limiter les breaking changes pour les apps clientes.
Conventions (recommandées)
ÉlĂ©mentConventionExemple
Nomspkg_ + domainepkg_order
Procéduresverbe + objetcreate_order
ParamĂštresp_ / o_p_id / o_status
Privéshelpers dans bodyvalidate_x()
Rule of thumb : ton app devrait appeler pkg_domain.action(), pas manipuler 12 tables directement.
3.2 — Triggers : usages, audit, piùges, alternatives
Trigger : exemple simple
CREATE OR REPLACE TRIGGER trg_customers_ts
BEFORE INSERT OR UPDATE ON customers
FOR EACH ROW
BEGIN
  :NEW.updated_at := SYSTIMESTAMP;
  IF INSERTING THEN
    :NEW.created_at := SYSTIMESTAMP;
  END IF;
END;
/
Bon usage : timestamps, contraintes techniques, enrichissement simple et local.
Audit (pattern classique)
CREATE OR REPLACE TRIGGER trg_customers_audit
AFTER INSERT OR UPDATE OR DELETE ON customers
FOR EACH ROW
BEGIN
  INSERT INTO audit_log(table_name, action, pk, at_ts)
  VALUES('CUSTOMERS',
         CASE WHEN INSERTING THEN 'I' WHEN UPDATING THEN 'U' ELSE 'D' END,
         NVL(:NEW.customer_id, :OLD.customer_id),
         SYSTIMESTAMP);
END;
/
Conseil : garder l’audit minimal et non bloquant (Ă©viter logique mĂ©tier lourde dans le trigger).
PiÚges (trÚs fréquents)
PiÚgeConséquenceSignal
Trigger qui appelle d’autres tables / commitsperf + blocages + complexitĂ©latence imprĂ©visible
Cascadeseffets de bord invisiblesbug “fantîme”
Mutating tableerreurs runtimeORA-04091
Logique métier lourdehard to test, hard to reasonchangement coûteux
Alternatives recommandées
  • Contraintes (NOT NULL, CHECK, FK, UNIQUE) : simples, rapides, explicites.
  • Packages API : le mĂ©tier est dans l’API, pas dans des triggers cachĂ©s.
  • Jobs (DB scheduler) : traitements asynchrones (purges, recalculs).
  • Audit applicatif : pour besoins riches (qui/quand/quoi + contexte).
4.1 — SQL dynamique : EXECUTE IMMEDIATE, binds, DBMS_SQL, sĂ©curitĂ©
EXECUTE IMMEDIATE (avec bind variables)
DECLARE
  v_sql   VARCHAR2(4000);
  v_cnt   NUMBER;
  p_status VARCHAR2(20) := 'ACTIVE';
BEGIN
  v_sql := 'SELECT COUNT(*) FROM customers WHERE status = :1';
  EXECUTE IMMEDIATE v_sql INTO v_cnt USING p_status;
  DBMS_OUTPUT.PUT_LINE('cnt='||v_cnt);
END;
/
DML dynamique (bind)
v_sql := 'UPDATE customers SET status = :1 WHERE customer_id = :2';
EXECUTE IMMEDIATE v_sql USING 'INACTIVE', 1234;
Ce qu’il ne faut PAS faire
-- ❌ Injection + hard parse :
v_sql := 'SELECT * FROM customers WHERE email = ''' || p_email || '''';
RÚgles de sécurité
  • Toujours utiliser des bind variables pour les valeurs.
  • Si besoin d’objets dynamiques (table/col) : whitelists + DBMS_ASSERT.
  • Limiter les privilĂšges (least privilege).
Perf : concaténer des valeurs = hard parse fréquent. Les binds aident cache de curseurs / stabilité.
Quand DBMS_SQL ?

DBMS_SQL est utile quand tu as des requĂȘtes dynamiques trĂšs gĂ©nĂ©riques (colonnes inconnues Ă  compile-time, metadata, parsing avancĂ©). Sinon, EXECUTE IMMEDIATE suffit dans la majoritĂ© des cas.

4.2 — Exceptions : stratĂ©gie d’erreurs, RAISE, RAISE_APPLICATION_ERROR
Exceptions Oracle courantes
ExceptionQuandTraitement
NO_DATA_FOUNDSELECT INTO 0 ligneretour défaut / gestion métier
TOO_MANY_ROWSSELECT INTO >1corriger filtre / unicité
ZERO_DIVIDEdivision par 0guard clause
DUP_VAL_ON_INDEXviolation uniqueupsert/merge, message clair
OTHERScatch-alllog + re-raise (souvent)
RAISE_APPLICATION_ERROR (métier)
IF p_amount <= 0 THEN
  RAISE_APPLICATION_ERROR(-20001, 'Amount must be > 0');
END IF;
Codes usuels : -20000 à -20999 (réservés custom).
Stratégie pro (recommandée)
1) On traite localement si on peut résoudre (NO_DATA_FOUND => status='UNKNOWN') 2) Sinon : log + RAISE (ne pas avaler) 3) Toujours renvoyer un message exploitable (sans exposer secrets) 4) Standardiser le logging (table log, package logger)
Logging minimal (pattern)
BEGIN
  -- ...
EXCEPTION
  WHEN OTHERS THEN
    INSERT INTO app_error_log(at_ts, code, msg, backtrace)
    VALUES(SYSTIMESTAMP, SQLCODE, SQLERRM, DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
    RAISE;
END;
Anti-pattern : faire WHEN OTHERS THEN NULL; (ça masque les pannes).
5.1 — Performance PL/SQL : bulk, instrumentation, plan SQL, context switches
Checklist perf (ultra utile)
PointPourquoiAction
Row-by-row ?lent sur gros volumesréécrire en set-based / BULK / FORALL
Context switchescoĂ»t PL/SQL↔SQLregrouper DML / bulk
Indexes / statsplan stableanalyser stats, revoir indexes
Hard parseCPUbind vars, éviter concat SQL
Instrumentationsans métriques = au hasardtimers + logs + views perf
Bulk processing : la technique qui change tout
-- Pattern batch:
FETCH ... BULK COLLECT ... LIMIT N
FORALL ... DML
COMMIT (si batchs indépendants) / ou commit global selon cohérence
But : traiter 1000 lignes en 1 roundtrip, pas 1000 roundtrips.
Instrumentation (minimum viable)
DECLARE
  t0 NUMBER := DBMS_UTILITY.GET_TIME; -- centiĂšmes
BEGIN
  -- traitement
  DBMS_OUTPUT.PUT_LINE('elapsed cs='||(DBMS_UTILITY.GET_TIME - t0));
END;
/

En prod, préfÚre un logger (table + contexte) plutÎt que DBMS_OUTPUT. Ajoute : nom job, paramÚtres, counts, durée, erreurs, backtrace.

Le plan SQL (toujours regarder)
Si c’est lent, 80% du temps c’est: - un plan SQL mauvais (stats/index) - une cardinalitĂ© mal estimĂ©e - un row-by-row cĂŽtĂ© PL/SQL Donc: EXPLAIN PLAN / DBMS_XPLAN + stats + rewriting.
Suggestion IDEO-Lab : intĂ©grer ton “Query Plan Auditor / EXPLAIN farm” pour auditer les requĂȘtes utilisĂ©es dans les packages.
5.2 — SĂ©curitĂ© PL/SQL : AUTHID, grants, least privilege, dynamic SQL safe
Definer rights vs Invoker rights
ModeCléEffetQuand
Definerpar défautexécute avec droits du propriétaireAPI DB classique
InvokerAUTHID CURRENT_USERdroits de l’appelantoutils gĂ©nĂ©riques, multi-schemas
CREATE OR REPLACE PACKAGE pkg_admin AUTHID CURRENT_USER AS
  PROCEDURE do_admin_task;
END;
/
Rùgles “least privilege”
  • Donner le minimum de GRANT nĂ©cessaire (SELECT/EXECUTE ciblĂ©s).
  • Éviter de sur-privilĂ©gier un schĂ©ma “app” (limiter CREATE ANY / etc.).
  • Auditer les EXECUTE sur packages sensibles.
  • Dynamic SQL : bind + whitelist + DBMS_ASSERT.
Erreur classique : “ça marche en DEV” (car schema owner) mais en PROD avec user restreint, ça casse. => dĂ©finir clairement les droits d’exĂ©cution et tester avec un compte “rĂ©aliste”.
6.1 — Tests & qualitĂ© : utPLSQL, jeux de donnĂ©es, transactions, CI
Approche testable (réaliste)
  • Écrire des packages “pur mĂ©tier” (fonctions) testables sans dĂ©pendances externes.
  • Isoler la logique DML dans des procĂ©dures, tester avec datasets contrĂŽlĂ©s.
  • Utiliser des transactions / savepoints pour rollback entre tests.
  • Centraliser des utilitaires de test (seed data, cleanup).
utPLSQL (idée)
-- Ex pseudo (concept)
-- describe('pkg_customer')
-- it('should create customer')
-- expect(select count(*) ...).to_equal(1)
CI / pipeline (piste)
1) Déployer schéma sur DB de CI (container / instance) 2) Appliquer migrations (Liquibase/Flyway) 3) Déployer packages 4) Lancer tests (utPLSQL) 5) Générer rapport + gate
Si tu veux, on peut faire une modal dĂ©diĂ©e “Liquibase/Flyway + PL/SQL + CI” adaptĂ©e Ă  ton infra IDEO-Lab.
6.2 — Outils & workflow : SQL Developer, SQLcl, migrations, style, Git
OutilRĂŽlePourquoi utileTips
SQL DeveloperIDE Oracledebug PL/SQL, objets DBprofilers, explain plan
SQLclCLI Oraclescripts, automation, CIidéal pipeline
Liquibase / Flywaymigrationsversioning schémaséparer DDL vs code
Gitversioningreview, history, tags release1 fichier = 1 objet
Formatter/Stylelisibilitéréduire friction équipeconventions + lint
Convention “1 objet = 1 fichier” (recommandĂ©e)
db/
  packages/
    pkg_customer.pks
    pkg_customer.pkb
  procedures/
    p_rebuild_indexes.sql
  triggers/
    trg_customers_audit.sql
PL/SQL — Cheat-sheet (ultra pratique)
Syntaxe
-- bloc
DECLARE ... BEGIN ... EXCEPTION ... END; /

-- select into
SELECT col INTO v FROM t WHERE ...;

-- insert returning
INSERT ... RETURNING id INTO v_id;

-- exceptions
WHEN NO_DATA_FOUND THEN ...
WHEN OTHERS THEN ... SQLERRM ...

-- procedure/function
PROCEDURE p(x IN NUMBER, o OUT NUMBER);
FUNCTION f(x IN NUMBER) RETURN VARCHAR2;

-- package
PACKAGE spec / PACKAGE BODY
Perf & sécurité
-- bulk (volumes)
BULK COLLECT ... LIMIT
FORALL ... DML

-- dynamic SQL safe
EXECUTE IMMEDIATE '... :1 ...' USING v;

-- droits
AUTHID CURRENT_USER (invoker)
sinon definer rights

-- log erreurs
SQLCODE / SQLERRM / FORMAT_ERROR_BACKTRACE
Top 3 “pro” : 1) BULK/FORALL, 2) Packages API, 3) Dynamic SQL uniquement avec binds.
PL/SQL — Questions d’entretien (junior → senior) + rĂ©ponses attendues
NiveauQuestionCe qu’on attend
JuniorDifférence procédure vs fonction ?RETURN, usages, params OUT
JuniorNO_DATA_FOUND / TOO_MANY_ROWS ?SELECT INTO et gestion
ConfirméPourquoi FORALL est plus rapide ?context switches + bulk DML
ConfirmĂ©Spec vs Body d’un package ?contrat public vs impl
SeniorAUTHID CURRENT_USER vs definer ?droits d’exĂ©cution + sĂ©curitĂ©
SeniorDynamic SQL : comment éviter injection ?bind vars + whitelist + DBMS_ASSERT
SeniorTriggers : quand éviter ?complexité/perf + alternatives
SeniorComment diagnostiquer une lenteur ?plan SQL + stats + instrumentation + rewrite
Le vrai “signal senior” : parler de perf (set-based, bulk), de sĂ©curitĂ© (rights/binds), et de design API (packages) plutĂŽt que de “syntax only”.