356 lines
9.5 KiB
Markdown
356 lines
9.5 KiB
Markdown
# Modélisation d'un système bancaire
|
||
|
||
Une banque locale souhaite informatiser la gestion de ses titulaires et de leurs comptes.
|
||
|
||
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,
|
||
2. Les comptes bancaires,
|
||
3. Le lien entre les titulaires et les comptes.
|
||
4. Les dépots et les retraits d'argent.
|
||
5. Les virements entre compte.
|
||
6. Les devises et les taux de change.
|
||
|
||
|
||
L'objectif de ces travaux pratiques est de :
|
||
|
||
- 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.
|
||
|
||
La diffusion de cette application est internationale, vous vous efforcerez d'utiliser des termes anglais pour nommer les entités et les propriétés. Réferrez pour cela vous au [glossaire](banque.glossaire.md)
|
||
|
||
Pour les entités vous utiliserez le singuler et écrirez le tout en minuscule.
|
||
|
||
## 1. Les titulaires
|
||
|
||
Un clients de la banque est appelé un titulaire. Il peut être une personne physique ou une entreprise.
|
||
|
||
### 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 août 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. 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.5 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. Il n'y a pas de restriction sur l'âge de la société.
|
||
|
||
### 1.6 Conclusion
|
||
|
||
#### Pourquoi séparer `person` et `company` ?
|
||
|
||
Parce que leurs attributs diffèrent (nom/prénom vs raison sociale).
|
||
Cela évite les **colonnes inutiles** et permet des **contraintes spécifiques** à chaque type.
|
||
|
||
#### Quelle contrainte empêche d’insérer une person sans holder ?
|
||
|
||
La clé étrangère `references holder(id)` dans `person`.
|
||
|
||
```mermaid
|
||
erDiagram
|
||
person {
|
||
bigint id PK
|
||
text firstname
|
||
text lastname
|
||
date birthdate
|
||
}
|
||
|
||
company {
|
||
bigint id PK
|
||
text registration_numer
|
||
date creation_date
|
||
}
|
||
|
||
holder {
|
||
bigint id PK
|
||
int person_id FK
|
||
}
|
||
|
||
%% Relations
|
||
|
||
person ||--|| holder
|
||
company ||--|| holder
|
||
```
|
||
|
||
## 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 (_balance_) et d'une date d'ouverture.
|
||
- 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 à 50/50 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 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;
|
||
```
|
||
|
||
```mermaid
|
||
erDiagram
|
||
person {
|
||
bigint id PK
|
||
text firstname
|
||
text lastname
|
||
date birthdate
|
||
}
|
||
|
||
company {
|
||
bigint id PK
|
||
text registration_numer
|
||
date creation_date
|
||
}
|
||
|
||
bank {
|
||
bigint id PK
|
||
}
|
||
|
||
holder {
|
||
bigint id PK
|
||
date creation_date
|
||
}
|
||
|
||
account {
|
||
bigint id PK
|
||
date creation_date
|
||
decimal balance >0
|
||
text currency_code FK
|
||
}
|
||
|
||
account_holder {
|
||
bigint account_id FK
|
||
bigint holder_id FK
|
||
}
|
||
|
||
transaction {
|
||
bigint id PK
|
||
date transaction_date
|
||
decimal amount
|
||
}
|
||
|
||
currency {
|
||
text code PK
|
||
}
|
||
|
||
currency {
|
||
text code PK
|
||
}
|
||
|
||
exchange_rate {
|
||
date date PK
|
||
text currency_code PK/FK
|
||
decimal rate
|
||
}
|
||
|
||
transaction {
|
||
bigint id PK
|
||
date date
|
||
decimal amount
|
||
}
|
||
|
||
%% Relations
|
||
|
||
person ||--|| holder :
|
||
company ||--|| holder :
|
||
bank ||--|| holder :
|
||
holder }|--|{ account_holder :
|
||
account_holder }|--|{ account :
|
||
currency ||--|{ account :
|
||
exchange_rate }o--|| currency :
|
||
```
|
||
|
||
## 4. 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.
|
||
|
||
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;
|
||
$$;
|
||
```
|
||
|
||
Pour afficher un message de réussite :
|
||
|
||
```sql
|
||
raise notice 'message %', variable;
|
||
```
|
||
|
||
Vous pouvez déclencher une erreur métier à l’aide de :
|
||
|
||
```sql
|
||
raise exception 'message';
|
||
```
|
||
|
||
Pour appeler la procédure stockée
|
||
|
||
```sql
|
||
call create_person_holder('Félicien', 'Hébrard', '1970-10-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 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_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
|
||
```
|
||
|
||
## La double écriture comptable
|
||
|
||
Jusqu’à présent, les dépôts et retraits modifiaient directement le solde d’un compte.
|
||
Mais dans un vrai système bancaire ou comptable, chaque opération financière doit être enregistrée en double :
|
||
|
||
➕ Un débit sur un compte (celui qui reçoit)
|
||
➖ Un crédit sur un autre (celui qui cède)
|
||
|
||
La somme des débits doit toujours être égale à la somme des crédits.
|