Reconstituer l’état à partir des événements
Intégrez les événements pour obtenir l’état actuel.
Reconstituer l’état à partir des événements est une leçon SQL Academy gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage SQL Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours SQL Academy comprend 4 leçons au total.
Que signifie reconstituer l’état ?
Dans la reconstitution de l’état à partir d’événements, les données sont stockées sous forme de journal immuable d’événements plutôt que de lignes modifiables. Pour connaître l’état actuel d’un élément, vous devez rejouer ces événements et les combiner en un résultat unique.
C’est ce qu’on appelle reconstituer l’état à partir d’événements. Pensez à un compte bancaire : au lieu de stocker le solde, vous stockez chaque dépôt et chaque retrait. Le solde correspond toujours à la somme de tous ces événements.
Une table d’événements simple
Commençons par créer un journal d’événements minimal pour un système de comptes bancaires. Chaque ligne représente quelque chose qui s’est produit — un dépôt ou un retrait — avec le montant et l’horodatage.
Cette table n’est jamais mise à jour ni supprimée. Les nouveaux faits sont toujours ajoutés sous forme de nouvelles lignes.
CREATE TABLE account_events (
event_id SERIAL PRIMARY KEY,
account_id INT NOT NULL,
event_type VARCHAR(20) NOT NULL, -- 'deposit' or 'withdrawal'
amount NUMERIC(12, 2) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
INSERT INTO account_events (account_id, event_type, amount, created_at) VALUES
(1, 'deposit', 1000.00, '2024-01-01 09:00:00+00'),
(1, 'deposit', 500.00, '2024-01-03 14:00:00+00'),
(1, 'withdrawal', 200.00, '2024-01-05 10:00:00+00'),
(1, 'deposit', 300.00, '2024-01-07 11:00:00+00'),
(1, 'withdrawal', 150.00, '2024-01-09 16:00:00+00');Agréger les événements pour obtenir un solde
Pour reconstruire le solde actuel, nous agrégeons tous les événements. Les dépôts augmentent le solde et les retraits le diminuent. Une expression CASE nous permet d’attribuer le signe approprié à chaque type d’événement avant d’effectuer la somme.
Cette seule requête fournit l’état actuel, entièrement dérivé du journal historique des événements.
SELECT
account_id,
SUM(
CASE event_type
WHEN 'deposit' THEN amount
WHEN 'withdrawal' THEN -amount
ELSE 0
END
) AS current_balance
FROM account_events
WHERE account_id = 1
GROUP BY account_id;État à un instant donné
L’un des aspects les plus puissants de l’approvisionnement par événements est la possibilité de reconstruire l’état à n’importe quel instant. Ajoutez simplement un filtre WHERE created_at <= :target_time avant l’agrégation.
Vous obtenez ainsi une requête permettant de remonter dans le temps, sans devoir modifier le schéma : l’historique se trouve déjà dans le journal des événements.
-- What was the balance at the end of January 5th?
SELECT
account_id,
SUM(
CASE event_type
WHEN 'deposit' THEN amount
WHEN 'withdrawal' THEN -amount
ELSE 0
END
) AS balance_at_snapshot
FROM account_events
WHERE account_id = 1
AND created_at <= '2024-01-05 23:59:59+00'
GROUP BY account_id;Solde cumulé avec les fonctions de fenêtre
Au lieu de calculer un seul total, nous pouvons calculer un solde cumulé — le solde après chaque événement. La fonction de fenêtre SUM(...) OVER (ORDER BY ...) calcule la somme cumulée au fur et à mesure que les événements s’accumulent dans l’ordre chronologique.
C’est particulièrement utile pour les pistes d’audit et le débogage des transitions d’état.
SELECT
event_id,
created_at,
event_type,
amount,
SUM(
CASE event_type
WHEN 'deposit' THEN amount
WHEN 'withdrawal' THEN -amount
ELSE 0
END
) OVER (PARTITION BY account_id ORDER BY created_at, event_id)
AS running_balance
FROM account_events
WHERE account_id = 1
ORDER BY created_at, event_id;Matérialiser l’état dans une table d’instantané
Rejouer tous les événements à chaque requête peut devenir coûteux à mesure que le journal s’agrandit. Une optimisation courante consiste à matérialiser l’état actuel dans une table d’instantané, puis à la reconstruire périodiquement ou à la demande.
L’instantané stocke le résultat agrégé ; les requêtes le lisent au lieu de rejouer l’intégralité du journal à chaque fois.
CREATE TABLE account_snapshots (
account_id INT PRIMARY KEY,
current_balance NUMERIC(12, 2) NOT NULL,
as_of_event_id INT NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Populate / refresh the snapshot from the event log
INSERT INTO account_snapshots (account_id, current_balance, as_of_event_id, updated_at)
SELECT
account_id,
SUM(CASE event_type WHEN 'deposit' THEN amount WHEN 'withdrawal' THEN -amount ELSE 0 END),
MAX(event_id),
NOW()
FROM account_events
GROUP BY account_id
ON CONFLICT (account_id) DO UPDATE
SET current_balance = EXCLUDED.current_balance,
as_of_event_id = EXCLUDED.as_of_event_id,
updated_at = EXCLUDED.updated_at;Mises à jour incrémentielles de l’instantané
Lorsque de nouveaux événements arrivent, vous n’avez pas besoin de rejouer tout l’historique. Si vous avez enregistré le dernier event_id traité dans l’instantané, vous pouvez appliquer uniquement l’écart — les événements arrivés après la création de l’instantané.
Cette approche incrémentielle permet de garder les actualisations de l’instantané rapides, même avec de volumineux journaux.
-- Apply only new events since the last snapshot
UPDATE account_snapshots AS snap
SET
current_balance = snap.current_balance + delta.net,
as_of_event_id = delta.max_event_id,
updated_at = NOW()
FROM (
SELECT
ae.account_id,
SUM(CASE ae.event_type WHEN 'deposit' THEN ae.amount WHEN 'withdrawal' THEN -ae.amount ELSE 0 END) AS net,
MAX(ae.event_id) AS max_event_id
FROM account_events ae
JOIN account_snapshots s ON s.account_id = ae.account_id
WHERE ae.event_id > s.as_of_event_id
GROUP BY ae.account_id
) AS delta
WHERE snap.account_id = delta.account_id;Tables temporelles et SYSTEM VERSIONING
SQL:2011 a introduit les tables temporelles versionnées par le système, que la base de données gère elle-même. Chaque ligne reçoit automatiquement les colonnes valid_from et valid_to, gérées par le moteur.
PostgreSQL ne prend pas cette fonctionnalité en charge nativement, mais vous pouvez l’émuler. D’autres bases de données, comme MariaDB et SQL Server, prennent directement en charge WITH SYSTEM VERSIONING.
-- Emulating a temporal table in PostgreSQL
CREATE TABLE account_state_history (
account_id INT NOT NULL,
current_balance NUMERIC(12, 2) NOT NULL,
valid_from TIMESTAMPTZ NOT NULL,
valid_to TIMESTAMPTZ NOT NULL DEFAULT 'infinity'
);
-- Insert initial state
INSERT INTO account_state_history (account_id, current_balance, valid_from)
VALUES (1, 1000.00, '2024-01-01 09:00:00+00');
-- On update: close old row, insert new row
UPDATE account_state_history
SET valid_to = '2024-01-03 14:00:00+00'
WHERE account_id = 1 AND valid_to = 'infinity';
INSERT INTO account_state_history (account_id, current_balance, valid_from)
VALUES (1, 1500.00, '2024-01-03 14:00:00+00');Interroger l’historique temporel
Une fois la table temporelle émulée en place, vous pouvez déterminer quel était le solde à n’importe quel moment passé en filtrant sur l’intervalle de validité. La ligne dont l’intervalle contient l’horodatage cible représente l’état à cet instant.
Cette approche dissocie la logique des requêtes de la relecture des événements : la table d’historique des états contient déjà les résultats agrégés.
-- What was the account balance on January 4th?
SELECT
account_id,
current_balance,
valid_from,
valid_to
FROM account_state_history
WHERE account_id = 1
AND valid_from <= '2024-01-04 00:00:00+00'
AND valid_to > '2024-01-04 00:00:00+00';Approvisionnement par événements avec plusieurs entités
Les systèmes réels suivent simultanément les événements de nombreuses entités. Un journal d’événements partagé doté d’une colonne entity_id et d’une colonne entity_type vous permet de reconstruire l’état de n’importe quel objet à partir d’une seule table.
Ici, nous suivons les mouvements de stock de plusieurs produits. Reconstruire le stock actuel de chaque produit consiste encore une fois simplement à effectuer une agrégation par groupe.
CREATE TABLE inventory_events (
event_id SERIAL PRIMARY KEY,
product_id INT NOT NULL,
event_type VARCHAR(20) NOT NULL, -- 'received', 'shipped', 'adjusted'
quantity INT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
INSERT INTO inventory_events (product_id, event_type, quantity, created_at) VALUES
(101, 'received', 200, '2024-03-01 08:00:00+00'),
(101, 'shipped', 50, '2024-03-02 12:00:00+00'),
(101, 'shipped', 30, '2024-03-04 15:00:00+00'),
(102, 'received', 150, '2024-03-01 08:00:00+00'),
(102, 'adjusted', -10, '2024-03-03 09:00:00+00');
-- Rebuild current stock for all products
SELECT
product_id,
SUM(CASE event_type WHEN 'received' THEN quantity WHEN 'shipped' THEN -quantity ELSE quantity END) AS stock_on_hand
FROM inventory_events
GROUP BY product_id
ORDER BY product_id;Utiliser des CTE pour plus de clarté
Les requêtes de reconstruction d’état peuvent devenir complexes. Encapsuler l’étape d’agrégation dans un CTE améliore la lisibilité et permet de joindre proprement l’état reconstruit à d’autres tables.
Ici, nous reconstruisons les soldes des comptes, puis nous les joignons à une table de référence des comptes afin d’inclure les noms des propriétaires dans le résultat.
CREATE TABLE accounts (
account_id INT PRIMARY KEY,
owner_name VARCHAR(100) NOT NULL
);
INSERT INTO accounts (account_id, owner_name) VALUES
(1, 'Alice'),
(2, 'Bob');
INSERT INTO account_events (account_id, event_type, amount, created_at) VALUES
(2, 'deposit', 2000.00, '2024-01-02 10:00:00+00'),
(2, 'withdrawal', 400.00, '2024-01-06 11:00:00+00');
WITH rebuilt_balances AS (
SELECT
account_id,
SUM(CASE event_type WHEN 'deposit' THEN amount WHEN 'withdrawal' THEN -amount ELSE 0 END) AS balance
FROM account_events
GROUP BY account_id
)
SELECT
a.account_id,
a.owner_name,
rb.balance
FROM accounts a
JOIN rebuilt_balances rb USING (account_id)
ORDER BY a.account_id;Vérification des connaissances
Évaluez votre compréhension de la reconstruction d’un état à partir d’événements en SQL.
Récapitulatif de la leçon
Dans cette leçon, vous avez appris à reconstruire l’état actuel et l’état historique à partir d’un journal d’événements immuable avec SQL.
Points essentiels :
- L’état est dérivé en agrégeant (en cumulant) les événements avec une expression
CASEsignée dansSUM. - L’ajout d’un filtre d’horodatage fournit gratuitement des requêtes à un instant donné.
- Les fonctions de fenêtre produisent un état cumulé après chaque événement.
- Les tables d’instantanés matérialisent le résultat agrégé pour améliorer les performances ; les mises à jour incrémentielles n’appliquent que les nouveaux événements.
- Les tables temporelles émulées stockent des lignes d’état déjà agrégées, avec des intervalles de validité permettant de retrouver rapidement l’historique.
- Les CTE rendent les requêtes de reconstruction lisibles lorsque vous devez joindre l’état dérivé à d’autres tables.
Ces approches constituent la base des conceptions de bases de données fondées sur les événements et adaptées à l’audit.
Questions Fréquemment Posées
La leçon « Reconstituer l’état à partir des événements » est-elle gratuite ?
Oui — le texte complet de « Reconstituer l’état à partir des événements » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours SQL Academy, passe à CoddyKit PRO. Le cours SQL Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Reconstituer l’état à partir des événements » ?
Intégrez les événements pour obtenir l’état actuel. Tu pratiques SQL Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer SQL Academy ?
Aucune expérience préalable n'est requise. SQL Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.
Combien de temps prend la leçon « Reconstituer l’état à partir des événements » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon SQL Academy ?
Oui. Chaque leçon SQL Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Pourquoi conserver l’historique
- Tables d’événements en ajout uniquement
- Lignes temporelles et versionnées
- Reconstituer l’état à partir des événements