# Corrections ## 1. Les titulaires `person` : informations propres aux personnes physiques - prénom, nom, date de naissance `company` : informations propres aux entreprises - raison sociale, numéro d’immatriculation, date de création ### 1.6 Contôle de l'âge Contrainte déclarative ```sql alter table person add constraint chk_person_minimum_age check (birthdate <= current_date - interval '15 years'); ``` Contrainte procédurale avec un trigger : ```sql create or replace function check_person_age() returns trigger as $$ begin if new.birthdate > current_date - interval '15 years' then raise exception 'Holder must be at least 15 years old'; end if; return new; end; $$ language plpgsql; create trigger trg_check_person_age before insert or update on person for each row execute procedure check_person_age(); ``` ```sql insert into holder (type) values ('BANK') returning holder; insert into bank (id, name) values (1, 'Banque de l''Est'); ``` La création d'un titulaire s'effectue en deux étapes : - Créer un compte individuel pour _Françoise Zanetti_, née le 12 avril 1995. - Créer une entreprise nommée _Boulangerie de Valorgue_, créée le 19 août 2014, numéro d’immatriculation FR19803269968. - Ajouter un nouveau titulaire : _Justin Hébrard_ né le 11/03/1993. #### 1.1 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`. - 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. Il n'y a pas de restriction sur l'âge de la société. - Le type doit être contraint à `'PERSON'` ou `'COMPANY'`. Il existe deux méthodes pour gérer le type. 1. vérifier par la commande `CHECK` la validité de la valeur. 2. utiliser une énumération `enum`. #### 1.2 Vérifications - Lister tous les titulaires. Pour réutiliser rapidement la requête enregistrer la dans une vue. - Supprimer un titulaire, vérifier que cela supprime l'individu ou la société correspondante. #### 1.4 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 du titulaire est toujours présente **sans être rattachée** à une personne. On parle alors d'enregistrement **orphelin**. 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. ```sql begin; ... commit; ``` #### 1.5 Procédure stockée Pour fiabiliser le process et être sûr que l'execution s'effectue toujours dans une transaction, nous allons encapsuler la création d’un titulaire dans une **procédure stockée**. Créez une **procédure stockée** appelée `create_person_holder`. Cette procédure prend en paramètre : * le prénom (`p_firstname text`) * le nom (`p_lastname text`) * la date de naissance (`p_birthdate date`) Cette procédure doit : * Vérifier que la personne a **au moins 15 ans** ; * Créer automatiquement un enregistrement dans `holder` de type `'PERSON'`; * Récupérer l’identifiant généré et créer la fiche dans `person` ; * Afficher un message de confirmation. 3. Si la personne a moins de 15 ans : * La procédure doit **refuser la création** et afficher une erreur claire. >[!NOTE] > La procédure garantit l’**atomicité** : soit tout est créé, soit rien. ### 2. Les comptes - Le solde des comptes ne peuvent être négatifs. - Créez un compte joint à 50/50 pour Françoise et Justin. Écrire une requête pour vérifier la somme des parts 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. 1. Créez une **procédure stockée** `create_account` qui : * prend en paramètre : * `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 ``` ### 3. Monnaies et taux de change ```sql insert into currency values ('EUR'); insert into currency values ('YEN'); ``` ```sql insert into exchange_rate values ('YEN', '2025-11-03', 177.57), ('YEN', '2025-11-04', 176.39), ('YEN', '2025-11-05', 176.67), ('YEN', '2025-11-06', 177.15), ('YEN', '2025-11-07', 176.99); ``` # Séance 3 : Exploitation des données [Utiliser la correction](banque.correction.sql) de la base de données ## 1. Vue : taux de change de la veille Créer une vue *yesterday_exchange_rates* qui affiche pour **chaque devise** son taux de change de la veille présent dans `exchange_rate`. Si aucun taux du jour n’existe, la ligne ne doit pas apparaître. ## 2. Fonction : dernier taux connu pour une devise et une date données Créer une fonction *latest_exchange_rate* qui donne, pour une devise, son taux de change le plus récent : ## 3. Vue : liste détaillées des titulaires Créer une vue *holder_details* permettant d’afficher **tous les titulaires** (banque, personne ou entreprise) sous une forme unifiée. La vue doit contenir : * l’identifiant du titulaire * son type * un champ `display_name` calculé ainsi : * pour un titulaire de type *PERSON* : *firstname lastname* * pour un titulaire de type *COMPANY* : *name* * pour un titulaire de type *BANK* : *name* ## 4. Vue : liste des comptes avec devise et solde Créer une vue *account_detail* affichant : * l’identifiant du compte * la date d’ouverture * la devise * le solde * le nombre de titulaires du compte ## 5. Vue : comptes par titulaire Créer une vue *holder_accounts* permettant de lister les comptes détenus par chaque titulaire, avec : * le titulaire (id et type) * le nom du titulaire * l’identifiant du compte * la part détenue (`share`) * le solde total du compte La vue doit fusionner les informations venant de **holder, person et company**. ## 6. Vue : opérations enrichies Créer une vue *operation_details* affichant les opérations avec : * la date de l’opération * le compte impacté * la direction (DEBIT ou CREDIT) * le montant de l’opération * le montant signé (crédit positif, débit négatif) * le solde du compte **après l’opération** (bonus : fenêtre analytique) ## 7. Vue : solde converti en EUR Créer une vue *account_balance_eur* pour afficher : * compte * devise d’origine * solde original * taux de change correspondant à la date du jour * solde converti en EUR (solde × taux) ## 8. Vue : transactions complètes Créer une vue *transaction_summary* affichant un regroupement par transaction : * id de la transaction * date * montant total de la transaction (somme des opérations) * liste des comptes concernés (optionnel : concaténation) ## 9. Vue : comptes en découvert imminent Créer une vue *accounts_at_risk* listant les comptes dont le solde est inférieur à 50 (dans leur devise), ou qui auraient un solde négatif s'ils effectuaient un débit supplémentaire de 20. > Vérification simple : `balance - 20 < 0 OR balance < 50`. ## 10. Vue : âge des personnes Créer une vue *person_age* indiquant : * id * nom complet * date de naissance * âge en années (utiliser `age()`) ## 11. Vue : répartition des parts d’un compte Créer une vue *account_shares* affichant : * id du compte * nombre de titulaires * somme des parts * une colonne booléenne `is_valid` vérifiant si la somme = 1 > Objectif : vérifier que les parts des comptes joints sont bien réparties. ## 12. Vue : solde par titulaire Créer une vue *holder_total_balance* indiquant pour chaque titulaire : * id * type * nom * somme des soldes de tous ses comptes (pondérée par `share`), calcul : `total = SUM(share × balance)` ## 13. Vue : opérations d’un compte en sens unique Créer une vue *account_debits* listant uniquement les opérations de type DEBIT, avec : * date * compte * montant négatif Créer une seconde vue *account_credits* (montants positifs). ## 14. Vue : liste des entreprises avec ancienneté Créer une vue *company_age* indiquant : * id * name * registration_number * age de l’entreprise en années (`age(current_date, created_at)`) ## 15. Vue : recapitulatif bancaire complet Créer une vue *bank_overview* qui croise : * les titulaires * leurs comptes * leurs transactions * leurs opérations Voir les adresses des serveurs [postgreSQL](https://sources.neotech.fr/Universite/tp/src/branch/main/geii3_2025.md)