15 KiB
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 :
- Les clients de la banque,
- Les comptes bancaires,
- Le lien entre les clients et les comptes.
- Les dépots et les retraits d'argent.
- Les virements entre compte.
- 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
Pour les entités vous utiliserez le singuler et écrirez le tout en minuscule.
Séance 1 : Le Diagramme Entités Relations (ERD)
1. Les titulaires
Un client de la banque est appelé un titulaire. Il peut être une personne physique ou une entreprise.
- 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 tablespersonetcompanyqui héritent logiquement de celle-ci.
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.
erDiagram
person {
bigint id PK
text firstname
text lastname
date birthdate
}
company {
bigint id PK
text registration_number
date creation_date
}
holder {
bigint id PK
timestamp creation_date
text type
}
%% Relations
person |o--|| holder : is
company |o--|| holder : is
2. Les comptes
- Chaque titulaire peut détenir un ou plusieurs comptes.
- Un compte bancaire doit pouvoir appartenir à un ou plusieurs titulaires (compte individuel / compte joint).
- Chaque compte dispose d’un numéro de compte unique, d’un solde et d'une date d'ouverture.
- Dans le cas d'un compte joint, les parts de propriété d'un compte doivent pouvoir être précisées.
erDiagram
person {
bigint id PK
text firstname
text lastname
date birthdate
}
company {
bigint id PK
text registration_number
date creation_date
}
bank {
bigint id PK
}
holder {
bigint id PK
timestamp creation_date
text type
}
account {
bigint id PK
date creation_date
decimal balance
text currency_code FK
}
account_holder {
bigint account_id FK
bigint holder_id FK
decimal share
}
%% Relations
person |o--|| holder : is
company |o--|| holder : is
bank |o--|| holder : is
holder }|--|{ account_holder : a
account_holder }|--|{ account : hold
3. Les opérations
erDiagram
person {
bigint id PK
text firstname
text lastname
date birthdate
}
company {
bigint id PK
text registration_number
date creation_date
}
bank {
bigint id PK
}
holder {
bigint id PK
timestamp creation_date
text type
}
account {
bigint id PK
date creation_date
decimal balance
text currency_code FK
}
account_holder {
bigint account_id FK
bigint holder_id FK
decimal share
}
operation {
bigint id PK
bigint transaction_id FK
text account FK
decimal amount
text direction
}
%% Relations
person |o--|| holder : is
company |o--|| holder : is
bank |o--|| holder : is
holder }|--|{ account_holder : a
account_holder }|--|{ account : hold
operation }o--|| account : concerne
4. Les transactions
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.
Chaque opération comporte au moins :
- un compte concerné ;
- une date ;
- un montant ;
- un sens (débit ou crédit).
Méthode 1 : montant relatif (positif/négatif)
On stocke un seul champ montant.
- Crédit → montant positif
- Débit → montant négatif
Exemple
| id | compte | date | montant |
|---|---|---|---|
| 1 | 123 | 2025-11-06 | +200.00 |
| 2 | 123 | 2025-11-07 | -50.00 |
Avantages
- Simple à manipuler pour les calculs (sommes, soldes, agrégations).
- Pas besoin de colonne supplémentaire pour le sens.
- Représente naturellement le comportement du solde d’un compte.
Inconvénients
- Le signe a une signification métier implicite → risque d’erreur d’interprétation.
- Moins lisible pour les utilisateurs finaux ou pour des exports comptables.
- Pas toujours compatible avec les règles de la comptabilité en partie double (où débit et crédit doivent être visibles séparément).
Méthode 2 : deux colonnes (débit / crédit)
Deux colonnes numériques, l’une pour le débit, l’autre pour le crédit. Une seule des deux contient une valeur non nulle.
Exemple
| id | compte | date | debit | credit |
|---|---|---|---|---|
| 1 | 123 | 2025-11-06 | 0.00 | 200.00 |
| 2 | 123 | 2025-11-07 | 50.00 | 0.00 |
Avantages
- Très clair visuellement et conforme aux usages comptables.
- Facilite les exports vers des logiciels comptables.
- On peut facilement filtrer les débits et crédits séparément.
Inconvénients
- Redondance potentielle (une des deux colonnes sera toujours à zéro).
- Les calculs de soldes nécessitent des expressions plus complexes :
SUM(credit) - SUM(debit) - Risque d’incohérence si les deux colonnes contiennent des valeurs remplies par erreur.
Méthode 3 : montant absolu + colonne sens ('D' / 'C')
Le montant est toujours positif.
Le sens est indiqué par une lettre (D ou C).
Exemple
| id | compte | date | montant | sens |
|---|---|---|---|---|
| 1 | 123 | 2025-11-06 | 200.00 | C |
| 2 | 123 | 2025-11-07 | 50.00 | D |
Avantages
- Lisible et intuitif : correspond au vocabulaire métier.
- Facilite la lecture humaine et les exports comptables.
- Pas d’ambiguïté sur le signe numérique.
Inconvénients
- Nécessite une jointure logique du sens pour les calculs :
SUM(CASE WHEN sens='C' THEN montant ELSE -montant END) - Moins direct pour les traitements purement mathématiques.
- L’usage de lettres rend le stockage un peu moins compact (mais négligeable en pratique).
Méthode 4 : montant absolu + colonne sens numérique (1 / -1)
Le montant est toujours positif, et une colonne numérique sens vaut 1 (crédit) ou -1 (débit).
Exemple
| id | compte | date | montant | sens |
|---|---|---|---|---|
| 1 | 123 | 2025-11-06 | 200.00 | 1 |
| 2 | 123 | 2025-11-07 | 50.00 | -1 |
Avantages
- Compact et performant pour les calculs :
SUM(montant * sens)donne directement le solde. - Moins d’ambiguïté qu’un signe caché dans le montant.
- Combine la rigueur du modèle mathématique avec la clarté du stockage absolu.
Inconvénients
- Moins lisible pour un utilisateur non technique.
- Moins standard pour la comptabilité classique (on préfère
D/C). - Nécessite une convention claire sur la signification de
1et-1.
Synthèse comparative
| Méthode | Structure | Lisibilité | Facilité calculs | Conformité comptable | Risque d'erreur |
|---|---|---|---|---|---|
| 1 | montant relatif (+/-) | ★☆☆ | ★★★ | ★☆☆ | Moyen |
| 2 | débit / crédit séparés | ★★★ | ★☆☆ | ★★★ | Faible |
| 3 | valeur + sens 'D'/'C' | ★★★ | ★★☆ | ★★★ | Faible |
| 4 | valeur + sens 1/-1 | ★★☆ | ★★★ | ★★☆ | Moyen |
5. Les devises
erDiagram
person {
bigint id PK
text firstname
text lastname
date birthdate
}
company {
bigint id PK
text registration_number
date creation_date
}
bank {
bigint id PK
text name
}
holder {
bigint id PK
timestamp creation_date
text type
}
account {
bigint id PK
date creation_date
decimal balance
text currency_code FK
}
account_holder {
bigint account_id FK
bigint holder_id FK
decimal share
}
currency {
text code PK
}
exchange_rate {
date date PK
text currency_code PK
decimal rate
}
transaction {
bigint id PK
date date
decimal amount
}
operation {
bigint id PK
bigint transaction_id FK
text account FK
decimal amount
text direction
}
%% Relations
person |o--|| holder : is
company |o--|| holder : is
bank |o--|| holder : is
holder }|--|{ account_holder : a
account_holder }|--|{ account : hold
currency ||--|{ account : tenu
exchange_rate }o--|| currency : a
transaction }o--|| operation : contient
operation }o--|| account : concerne
operation }o--|| account : concerne
Séance 2 : Implémentation du modèle
Voir la syntaxe de postgreSQL
Voir les adresses des serveurs postgreSQL
1. Titulaires
- 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
personoucompanydoit correspondre à exactement un seulholder. - La suppression d’un
holderdoit supprimer automatiquement la ligne correspondante danspersonoucompany. - Le type doit être contraint à
'PERSON'ou'COMPANY'.
Il existe deux méthodes pour gérer le type.
-
vérifier par la commande
CHECKla validité de la valeur. -
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.3 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.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.
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
holderde type'PERSON'; - Récupérer l’identifiant généré et créer la fiche dans
person; - Afficher un message de confirmation.
- 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.
- Créez une procédure stockée
create_accountqui :
-
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_holdersetp_sharesest identique ; - la somme des parts est exactement égale à 1 ;
- chaque
holder_idexiste dans la tableholder.
- le nombre d’éléments dans
- Si tout est correct :
- crée un nouveau compte dans
account; - insère les lignes correspondantes dans
account_holder.
- 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 :
for i in 1..array_length(p_holders, 1) loop
...
end loop;
- Pour vérifier la somme :
select sum(unnest(p_shares));
(ou additionner dans la boucle)
- Vous pouvez lever une erreur personnalisée :
raise exception 'La somme des parts doit être égale à 1 (%.4f)', somme;
Exemples d’appel
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.
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. Les opérations et transactions
Implémenter les transactions et opérations.