diff --git a/banque.correction.md b/banque.correction.md index 8091f23..1dcb312 100644 --- a/banque.correction.md +++ b/banque.correction.md @@ -35,3 +35,341 @@ 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) diff --git a/banque.md b/banque.md index fea098d..af2feab 100644 --- a/banque.md +++ b/banque.md @@ -32,341 +32,4 @@ Pour les entités vous utiliserez le singuler et écrirez le tout en minuscule. - [Les vues](banque.vues.md) - [Les fonctions](banque.functions.md) -Voir les adresses des serveurs [postgreSQL](https://sources.neotech.fr/Universite/tp/src/branch/main/geii3_2025.md) - Voir la syntaxe de [postgreSQL](syntaxe.md) - -```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 diff --git a/banque.tables.md b/banque.tables.md index 30be234..993b4ee 100644 --- a/banque.tables.md +++ b/banque.tables.md @@ -2,6 +2,10 @@ ## 1. Les titulaires (holder) +Une énumération (souvent appelée enum) est un type de données qui permet de définir un ensemble limité et nommé de valeurs possibles. + +Une énumération sert à représenter une liste finie de choix clairement définis. + ```sql create type holder_type as enum ('BANK', 'PERSON', 'COMPANY'); @@ -70,8 +74,6 @@ create table company ( - `unique` : La valeur doit être unique (si elle est renseignée). Contrairement à une clé primaire la valeur peut être null. - - ## 2. Les devises ```sql @@ -135,4 +137,4 @@ create table operation ( ); ``` -Il n'est pas nécessaire d'utiliser une énumération pour contraindre les deux valeurs de direction. +Il n'est pas nécessaire d'utiliser une énumération pour contraindre les deux valeurs de direction. Une contrainte check est suffisante. diff --git a/banque.vues.md b/banque.vues.md index a262c98..0dedf5c 100644 --- a/banque.vues.md +++ b/banque.vues.md @@ -1,17 +1,26 @@ +# Les vues + +Une vue SQL est un objet de la base de données qui représente le résultat d’une requête SQL enregistrée. +Elle se comporte comme une table virtuelle : on peut la consulter avec SELECT comme une table, mais elle ne stocke pas de données. + +La définition de la vue est enregistrée dans la base, et chaque fois qu’on interroge la vue, la base réexécute la requête sous-jacente. + ```sql create or replace view holder_detail as select h.id, h.type, case - when type = 'PERSON' then firstname || ' ' || lastname - else name + when type = 'PERSON' then firstname || ' ' || lastname + when type = 'COMPANY' then c.name + when type = 'BANK' then b.name end as nom, case - when type = 'PERSON' then age(birthdate) - when type = 'COMPANY' then age(c.created_at) + when type = 'PERSON' then age(birthdate) + when type = 'COMPANY' then age(c.created_at) end as age from holder h left join person p on p.id = h.id left join company c on c.id = h.id; + left join bank b on b.id = h.id; ``` ```sql