Files
sql/banque.md
2025-11-14 16:26:31 +01:00

22 KiB
Raw Blame History

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 dimplé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 clients 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 dun scénario réaliste.
  • Utiliser les contraintes dintégrité pour garantir la cohérence des données.
  • Manipuler des jointures et des relations nn.
  • Comprendre la notion dhé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 tables person et company qui 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 dinsé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 name
    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 dun numéro de compte unique, dun 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 name
    text registration_number
    date creation_date
  }

  holder {
    bigint id PK
    timestamp creation_date
    text type
  }

  account {
    bigint id PK
    timestamp creation_date
    decimal balance
  }

  account_holder {
    bigint account_id FK
    bigint holder_id FK
    decimal share
  }

  %% Relations

  person  |o--|| holder : is
  company |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 name
    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
    timestamp 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
    bigint account_id 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 dun 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 dun compte.
Inconvénients
  • Le signe a une signification métier implicite → risque derreur dinterpré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, lune pour le débit, lautre 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 dincohé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 dambiguï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.
  • Lusage 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 dambiguïté quun 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 1 et -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 name
    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
    timestamp creation_date
    decimal balance
    text currency_code FK
  }

  account_holder {
    bigint account_id PK,FK
    bigint holder_id PK,FK
    decimal share
  }

  currency {
    text code PK
  }

  exchange_rate {
    date date PK
    text currency_code PK,FK
    decimal rate
  }

  transaction {
    bigint id PK
    timestamp transaction_date
    decimal amount
  }

  operation {
    bigint id PK
    bigint transaction_id FK
    bigint account_id 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 ||--|{ operation : a
  operation }o--|| account : a

Séance 2 : Implémentation du modèle

Voir les adresses des serveurs postgreSQL

Voir la syntaxe de postgreSQL

1. Titulaires

Création de la table holder.

create table holder (
  "id" bigint primary key generated always as identity,
  "type" text,
  "created_at" timestamp
);
  • entier sur 64 bits : bigint
  • clé primaire : primary key
  • incrément automatique : generated always as identity

Création de la table person.

create table person (
  "id" bigint primary key references holder(id),
  "firstname" text,
  "lastname" text,
  "birthdate" date
);
  • 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.
create table bank (
  "id" bigint primary key references holder(id),
  "name" text
);
insert into holder (type) values ('BANK') returning holder;
insert into bank (id, name) values (10, 'Banque de l''Est');

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.

create type holder_type as enum ('BANK', 'PERSON', 'COMPANY');

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.

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.
  1. 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 latomicité : soit tout est créé, soit rien.

2. Les comptes

create table account (
  "id" bigint primary key generated always as identity,
  "creation_date" date default current_date,
  "balance" decimal check (balance >= 0),
  "currency_code" text references currency (code)
);
  • 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.
  1. Si tout est correct :
  • crée un nouveau compte dans account ;
  • insère les lignes correspondantes dans account_holder.
  1. 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 dappel

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. Monnaies et taux de change

create table currency (
  "code" text primary key
);
insert into currency values ('EUR');
insert into currency values ('YEN');
create table exchange_rate (
  "currency_code" text references currency(code) on delete cascade,
  "date" date ,
  "rate" decimal not null,
  primary key (currency_code, date)
);
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);

4. Les opérations et transactions

Implémenter les transactions et opérations.

create table transaction (
  "id" bigint primary key generated always as identity,
  "transaction_date" timestamp default current_timestamp,
  "amount" decimal check (amount > 0)
);
create table operation (
  "id" bigint primary key generated always as identity,
  "transaction_id" bigint references transaction(id),
  "account_id" bigint references account(id),
  "amount" decimal check (amount > 0),
  "direction" text check (direction in ('DEBIT', 'CREDIT'))
);

Séance 3 : Exploitation des données

Ajouter des données

truncate table holder;
truncate table account;

call add_bank('Banque de l''Est');
insert into currency values ('EUR'),('YEN'),('USD');

call add_account(1,'EUR');
call add_account(1,'YEN');
call add_account(1,'USD');

update account set balance = 100000 where id = 1;
update account set balance = 50000 where id = 2;
update account set balance = 200000 where id = 3;

insert into exchange_rate values
('EUR', '1999-01-01', 1),
('USD', '2025-01-02', 1.0321),
('USD', '2025-02-03', 1.0274),
('USD', '2025-03-03', 1.0465);

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_summary 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