# Modélisation d'un système bancaire ## Objectifs - Concevoir un **modèle relationnel** à partir d’un scénario réaliste. - Utiliser les **contraintes d’intégrité** pour garantir la cohérence des données. - Manipuler des **jointures** et des **relations n–n**. - Comprendre la notion d’**héritage logique** en base de données. ## Contexte Une banque locale souhaite informatiser la gestion de ses comptes et de leurs titulaires. Chaque compte peut appartenir à une ou plusieurs personnes physiques ou morales. Vous êtes chargé(e) de concevoir et d’implémenter le schéma relationnel de base permettant de gérer : 1. Les clients de la banque, appelés titulaires (_holders_), 2. Les comptes bancaires (_accounts_), 3. Le lien entre les titulaires et les comptes. ## 1. Les titulaires Un titulaire (_holder_) peut être une personne physique (_person_) ou une entreprise (_company_). ### 1.1 Exemple de données - une personne nommée _Françoise Zanetti_, née le 12 avril 1995. - une entreprise nommée _Boulangerie de Valorgue_, créée le 19/08/2014, numéro d’immatriculation FR19803269968. ### 1.2 Analyse - Quelles informations faut-il conserver pour tous les titulaires ? - Quelles informations sont spécifiques à chaque type de titulaire ? - Comment représenter cette distinction en base relationnelle ? > [!TIP] > Indice : on peut utiliser une table abstraite `holder`, puis des tables `person` et `company` qui héritent logiquement de celle-ci. ### 1.3 Contraintes à respecter - Chaque `person` ou `company` doit correspondre à exactement un seul `holder`. - La suppression d’un `holder` doit supprimer automatiquement la ligne correspondante dans `person` ou `company`. - Le type doit être contraint à `'PERSON'` ou `'COMPANY'`. ### 1.4 Vérifications Lister tous les titulaires. Supprimer un titulaire, vérifier que cela supprime l'individu ou la société correspondante. ### 1.5 Réflexion 1. Pourquoi séparer `person` et `company` ? 2. Pourquoi ne pas tout mettre dans une seule table holder ? 3. Quelle contrainte empêche d’insérer une person sans holder ? ### 1.6 Pour aller plus loin La banque souhaite désormais que toute personne titulaire d’un compte ait au moins 15 ans à la date de création de sa fiche. ## 2. Les comptes - Chaque titulaire peut détenir un ou plusieurs compte. - Un compte bancaire doit pouvoir appartenir à un ou plusieurs titulaires (compte individuel / compte joint). - Chaque compte dispose d’un numéro de compte (_account number_) unique, d’un solde, d'une date d'ouverture et une date de clôture. - Le solde des comptes ne peuvent être négatifs. - Dans le cas d'un compte joint, les parts (_share_) de propriété d'un compte doivent pouvoir être précisées. ### 2.1 Exemple de données - Créez un compte individuel pour Françoise Zanetti. - Ajouter un nouveau titulaire : Justin Hébrard né le 11/03/1993. - Créez un compte joint pour Françoise et Justin. ## 3. L'intégrité des données Lorsque l’on tente d'insèrer une nouvelle personne qui n'a pas l'âge requis. La ligne dans holder est d'abord créée, puis l'insertion dans person échoue à cause de la vérification d'âge. Mais la ligne holder est toujours présente sans être rattachée à une personne. Chaque commande SQL est exécutée indépendamment. Si la deuxième commande échoue, la première n’est pas annulée automatiquement. Réalisez l’insertion d’un titulaire complet (dans holder et person) à l’aide d’une transaction. Testez le cas où la contrainte d’âge échoue et vérifiez que rien n’est inséré dans holder. ## 4. Procédure stockée Vous avez remarqué qu’en ajoutant une contrainte sur l’âge minimum du titulaire, une insertion invalide dans `person` peut échouer **après** la création du `holder`. Cela laisse un **titulaire orphelin** sans fiche personnelle. Dans un système bancaire réel, ce type d’incohérence est inacceptable. Nous allons donc **encapsuler la création d’un titulaire dans une procédure stockée** pour garantir la cohérence et la simplicité d’usage. 1. Créez une **procédure stockée** appelée `create_person_holder` * qui prend en paramètre : * le prénom (`p_firstname text`) * le nom (`p_lastname text`) * la date de naissance (`p_birthdate date`) 2. Cette procédure doit : * Vérifier que la personne a **au moins 15 ans** ; * Créer automatiquement un enregistrement dans `holder` (type = `'PERSON'`) ; * Récupérer l’identifiant généré et créer la fiche dans `person` ; * Afficher un message de confirmation avec `RAISE NOTICE`. 3. Si la personne a moins de 15 ans : * La procédure doit **refuser la création** et afficher une erreur claire (par `RAISE EXCEPTION`). En PL/pgSQL les **procédures** s’écrivent : ```sql create or replace procedure nom_procedure(paramètres) language plpgsql as $$ declare -- variables locales begin -- instructions SQL end; $$; ``` Vous pouvez intercepter une erreur métier à l’aide de : ```sql raise exception 'message'; ``` Pour afficher un message de réussite : ```sql raise notice 'message %', variable; ``` Pour appeler une procédure stockée ```sql call create_person_holder('Alice', 'Martin', '1990-03-15'); ``` >[!NOTE] > La procédure garantit l’**atomicité** : soit tout est créé, soit rien. ## 5. Cohérence des données Écrire une requête pour vérifier la somme Excellent 👌 — on va continuer dans la même logique de rigueur comptable et de progressivité pédagogique. Cette étape introduira une **seconde procédure stockée**, dédiée à la création de comptes, avec **contrôle des titulaires associés et de leurs parts (shares)**. --- # 🧩 TP Banque – Étape 5 : Créer un compte bancaire de manière cohérente --- ## 🎯 Objectif Jusqu’à présent, nous savons créer des titulaires (`holder`) de façon sûre. Il est temps de leur ouvrir des **comptes bancaires**. Un compte doit toujours : * être associé à **au moins un titulaire**, * et la somme des parts (`share`) des titulaires doit être exactement **égale à 1** (c’est-à-dire 100 % du compte). Nous allons donc écrire une **procédure stockée** `create_account` qui vérifie ces règles avant d’enregistrer les données. --- ## 📘 Contexte Les tables concernées sont les suivantes : ```sql create table account ( id serial primary key, iban text not null unique, name text not null, opened_on date not null default current_date ); create table account_holder ( account_id int not null references account(id) on delete cascade, holder_id int not null references holder(id) on delete restrict, share numeric(5,4) not null check (share > 0 and share <= 1), primary key (account_id, holder_id) ); ``` 👉 La table `account_holder` relie un compte à un ou plusieurs titulaires, avec la part de chacun. --- ## 🧱 Travail demandé 1. Créez une **procédure stockée** `create_account` qui : * prend en paramètre : * `p_iban text`, * `p_name text`, * `p_holders int[]` (tableau des identifiants de titulaires), * `p_shares numeric[]` (tableau des parts correspondantes). * vérifie que : * le nombre d’éléments dans `p_holders` et `p_shares` est identique ; * la somme des parts est **exactement égale à 1** ; * chaque `holder_id` existe dans la table `holder`. 2. Si tout est correct : * crée un nouveau compte dans `account` ; * insère les lignes correspondantes dans `account_holder`. 3. Si une condition échoue : * la procédure doit lever une **erreur explicite** (`RAISE EXCEPTION`) ; * aucune insertion ne doit être faite (transaction annulée). * Les tableaux peuvent être parcourus avec une boucle : ```sql for i in 1..array_length(p_holders, 1) loop ... end loop; ``` * Pour vérifier la somme : ```sql select sum(unnest(p_shares)); ``` (ou additionner dans la boucle) * Vous pouvez lever une erreur personnalisée : ```sql raise exception 'La somme des parts doit être égale à 1 (%.4f)', somme; ``` --- ### Exemples d’appel ```sql call create_account( 'FR761234567890', 'Compte commun', array[1, 5], array[0.5, 0.5] ); ``` Crée un compte partagé 50/50 entre les titulaires 1 et 2. ```sql call create_account( 'FR009999999999', 'Compte déséquilibré', array[1, 5], array[0.7, 0.4] ); ``` Doit refuser la création avec une erreur claire : ``` ERROR: La somme des parts (1.1000) doit être égale à 1.0000 ```