This commit is contained in:
2025-11-02 20:50:39 +01:00
parent 46db8f1e19
commit 1a06ea5bf2
3 changed files with 329 additions and 94 deletions

View File

@@ -2,76 +2,13 @@
## 1. Les titulaires
`holder` : table commune à tous les titulaires
- identifiant unique (`id`)
- type de titulaire (`type` = 'PERSON' ou 'COMPANY')
- date de création
```sql
create table holder (
id bigint primary key generated always as identity,
type text not null check (type in ('PERSON', 'COMPANY')),
created_at timestamp not null default now()
);
```
`person` : informations propres aux personnes physiques
- prénom, nom, date de naissance
```sql
create table person (
id bigint primary key references holder(id) on delete cascade,
firstname text not null,
lastname text not null,
birthdate date not null
);
```
`company` : informations propres aux entreprises
- raison sociale, numéro dimmatriculation, date de création
```sql
create table company (
id bigint primary key references holder(id) on delete cascade,
name text not null,
registration_number text unique not null,
created_at date not null
);
```
### Tests d'insertion des données
```sql
-- Création dun titulaire de type personne physique
insert into holder(type) values ('PERSON') returning id;
-- Utilisons l'id qui est retourné (1)
insert into person(id, firstname, lastname, birthdate)
values (1, 'Françoise', 'Zanetti', '1995-04-12');
```
```sql
-- Création dun titulaire de type entreprise
insert into holder(type) values ('COMPANY') returning id;
-- Utilisons l'id qui est retourné (2)
insert into company(id, name, registration_number, created_at)
values (2, 'Boulangerie de Valorgue', 'FR19803269968', '2014-08-19');
```
### Vérification
```sql
create view list_holders as
select h.id, h.type, h.created_at,
p.firstname || ' ' || p.lastname as person,
c.name as company
from holder h
left join person p on p.id = h.id
left join company c on c.id = h.id
order by h.id;
```
#### 1.5.1 Pourquoi séparer `person` et `company` ?
@@ -113,32 +50,3 @@ create trigger trg_check_person_age
before insert or update on person
for each row execute procedure check_person_age();
```
### 2. Les comptes
```sql
create table account (
id bigint primary key generated always as identity,
number text unique not null,
opened_at date not null default current_date,
closed_at date,
balance numeric(18,6) not null default 0 check (balance >= 0)
);
```
Chaque compte a un numéro unique.
La contrainte check (balance >= 0) empêche les soldes négatifs.
```sql
create table account_holder (
account_id int not null references account(id) on delete cascade,
holder_id int not null references holder(id) on delete cascade,
share numeric(4,3) check (share > 0 and share <= 1),
primary key (account_id, holder_id)
);
```
Cette table établit la relation nn entre account et holder.
La contrainte share assure que les parts sont comprises entre 0 et 1.
Un compte joint correspond donc à plusieurs lignes dans cette table.

209
banque.md
View File

@@ -71,3 +71,212 @@ La banque souhaite désormais que toute personne titulaire dun compte ait au
- 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 pour Françoise et Justin.
## 3. 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 holder est toujours présente sans être rattachée à une personne.
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.
## 4. Procédure stockée
Vous avez remarqué quen ajoutant une contrainte sur lâge minimum du titulaire, une insertion invalide dans `person` peut échouer **après** la création du `holder`.
Cela laisse un **titulaire orphelin** sans fiche personnelle.
Dans un système bancaire réel, ce type dincohérence est inacceptable.
Nous allons donc **encapsuler la création dun titulaire dans une procédure stockée** pour garantir la cohérence et la simplicité dusage.
1. Créez une **procédure stockée** appelée `create_person_holder`
* qui prend en paramètre :
* le prénom (`p_firstname text`)
* le nom (`p_lastname text`)
* la date de naissance (`p_birthdate date`)
2. Cette procédure doit :
* Vérifier que la personne a **au moins 15 ans** ;
* Créer automatiquement un enregistrement dans `holder` (type = `'PERSON'`) ;
* Récupérer lidentifiant généré et créer la fiche dans `person` ;
* Afficher un message de confirmation avec `RAISE NOTICE`.
3. Si la personne a moins de 15 ans :
* La procédure doit **refuser la création** et afficher une erreur claire (par `RAISE EXCEPTION`).
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;
$$;
```
Vous pouvez intercepter une erreur métier à laide de :
```sql
raise exception 'message';
```
Pour afficher un message de réussite :
```sql
raise notice 'message %', variable;
```
Pour appeler une procédure stockée
```sql
call create_person_holder('Alice', 'Martin', '1990-03-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
Excellent 👌 — on va continuer dans la même logique de rigueur comptable et de progressivité pédagogique.
Cette étape introduira une **seconde procédure stockée**, dédiée à la création de comptes, avec **contrôle des titulaires associés et de leurs parts (shares)**.
---
# 🧩 TP Banque Étape 5 : Créer un compte bancaire de manière cohérente
---
## 🎯 Objectif
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.
---
## 📘 Contexte
Les tables concernées sont les suivantes :
```sql
create table account (
id serial primary key,
iban text not null unique,
name text not null,
opened_on date not null default current_date
);
create table account_holder (
account_id int not null references account(id) on delete cascade,
holder_id int not null references holder(id) on delete restrict,
share numeric(5,4) not null check (share > 0 and share <= 1),
primary key (account_id, holder_id)
);
```
👉 La table `account_holder` relie un compte à un ou plusieurs titulaires, avec la part de chacun.
---
## 🧱 Travail demandé
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).
---
## 💡 Aide
* 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;
```
---
## ✅ Exemple dappel
### Cas valide :
```sql
call create_account(
'FR761234567890',
'Compte commun',
array[1, 2],
array[0.5, 0.5]
);
```
➡️ Crée un compte partagé 50/50 entre les titulaires 1 et 2.
### Cas invalide :
```sql
call create_account(
'FR009999999999',
'Compte déséquilibré',
array[1, 2],
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
```

View File

@@ -53,7 +53,7 @@ insert into company(id, name, registration_number, created_at)
*/
-- Liste de tous les titulaires
create view list_holders as
create view holders_list as
select h.id, h.type, h.created_at,
p.firstname || ' ' || p.lastname as person,
c.name as company
@@ -86,7 +86,7 @@ check (birthdate <= current_date - interval '15 years');
insert into holder(type) values ('PERSON') returning id;
insert into person(id, firstname, lastname, birthdate)
values (3, 'Mattéo', 'Zanetti', '2012-12-12');
values (4, 'Mattéo', 'Zanetti', '2012-12-12');
/*
* Account
@@ -146,3 +146,121 @@ insert into account_holder
insert into account_holder
values (2, 5, 0.5);
create view accounts_list as
select
a.number,
h.type,
coalesce(p.firstname || ' ' || p.lastname, c.name) as holder_name,
ah.share,
a.balance
from account a
join account_holder ah on ah.account_id = a.id
join holder h on h.id = ah.holder_id
left join person p on p.id = h.id
left join company c on c.id = h.id
order by a.id, h.id;
/*
* Une transaction
* L'enregistrement réel de toutes les entités s'effectue au commit
*/
begin;
insert into holder(type) values ('PERSON') returning id;
insert into person(id, firstname, lastname, birthdate)
values (6, 'Justin', 'Hébrard', '1993-03-11');
commit;
create or replace procedure create_person_holder(
p_firstname text,
p_lastname text,
p_birthdate date
)
language plpgsql
as $$
declare
v_holder_id bigint;
begin
-- Vérification métier
if p_birthdate > current_date - interval '15 years' then
raise exception 'Le titulaire doit avoir au moins 15 ans (%).', p_birthdate
using errcode = 'P0001';
end if;
-- Création atomique du titulaire
insert into holder(type)
values ('PERSON')
returning id into v_holder_id;
insert into person(id, firstname, lastname, birthdate)
values (v_holder_id, p_firstname, p_lastname, p_birthdate);
raise notice 'Titulaire créé avec succès : %, % % (ID=%)',
p_firstname, p_lastname, to_char(p_birthdate, 'YYYY-MM-DD'), v_holder_id;
end;
$$;
call create_person_holder('Mattéo', 'Zanetti', '2012-12-12');
call create_person_holder('Clotilde', 'Hébrard', '1989-07-14');
select account_id, sum(share) as total_share
from account_holder
group by account_id;
create or replace procedure create_account(
p_iban text,
p_name text,
p_holders int[],
p_shares numeric[]
)
language plpgsql
as $$
declare
v_account_id int;
v_sum numeric(6,4) := 0;
v_count int;
begin
-- Vérification des tailles
if array_length(p_holders, 1) is null or array_length(p_shares, 1) is null then
raise exception 'Les tableaux de titulaires et de parts ne peuvent pas être vides.';
end if;
if array_length(p_holders, 1) <> array_length(p_shares, 1) then
raise exception 'Les tableaux de titulaires et de parts doivent avoir la même taille.';
end if;
-- Calcul de la somme des parts
for i in 1..array_length(p_shares, 1) loop
v_sum := v_sum + p_shares[i];
end loop;
if abs(v_sum - 1.0) > 0.0001 then
raise exception 'La somme des parts (%.4f) doit être égale à 1.0000', v_sum;
end if;
-- Vérification des titulaires
select count(*) into v_count from holder where id = any(p_holders);
if v_count <> array_length(p_holders, 1) then
raise exception 'Un ou plusieurs titulaires n''existent pas.';
end if;
-- Création du compte
insert into account(iban, name)
values (p_iban, p_name)
returning id into v_account_id;
-- Association des titulaires
for i in 1..array_length(p_holders, 1) loop
insert into account_holder(account_id, holder_id, share)
values (v_account_id, p_holders[i], p_shares[i]);
end loop;
raise notice 'Compte créé avec succès (ID=%) avec % titulaires.',
v_account_id, array_length(p_holders, 1);
end;
$$;