Files
sql/banque.correction.md

376 lines
10 KiB
Markdown
Raw Normal View History

2025-11-02 14:36:58 +01:00
# 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 dimmatriculation, 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();
```
2025-11-18 20:40:28 +01:00
```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 dimmatriculation 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 dun `holder` doit supprimer automatiquement la ligne correspondante dans `person` ou `company`.
- La banque souhaite désormais que toute personne titulaire dun 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 lon 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 nest pas annulée automatiquement.
Réalisez linsertion dun titulaire complet (dans holder et person) à laide dune **transaction**.
Testez le cas où la contrainte dâge échoue et vérifiez que rien nest 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 dun 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 lidentifiant 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** (cest-à-dire 100 % du compte).
Nous allons donc écrire une **procédure stockée** `create_account` qui vérifie ces règles avant denregistrer 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 dappel
```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 nexiste, 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 dafficher **tous les titulaires** (banque, personne ou entreprise) sous une forme unifiée.
La vue doit contenir :
* lidentifiant 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 :
* lidentifiant du compte
* la date douverture
* 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
* lidentifiant 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 lopération
* le compte impacté
* la direction (DEBIT ou CREDIT)
* le montant de lopération
* le montant signé (crédit positif, débit négatif)
* le solde du compte **après lopération** (bonus : fenêtre analytique)
## 7. Vue : solde converti en EUR
Créer une vue *account_balance_eur* pour afficher :
* compte
* devise dorigine
* 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 dun 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 dun 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 lentreprise 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)