Reconstruindo o Estado a Partir de Eventos
Combine eventos para obter o estado atual.
Reconstruindo o Estado a Partir de Eventos é uma aula grátis de SQL Academy no CoddyKit. Esta é a aula 4 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de SQL Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de SQL Academy inclui 4 aulas no total.
O que significa reconstruir o estado?
Na origem de eventos, os dados são armazenados como um registro imutável de eventos, e não como linhas mutáveis. Para conhecer o estado atual de qualquer elemento, você precisa reproduzir esses eventos e agregá-los em um único resultado.
Isso é chamado de reconstrução do estado a partir de eventos. Pense em uma conta bancária: em vez de armazenar o saldo, você armazena cada depósito e saque. O saldo é sempre a soma de todos esses eventos.
Uma tabela simples de eventos
Vamos começar criando um registro mínimo de eventos para um sistema de contas bancárias. Cada linha representa algo que aconteceu — um depósito ou um saque — com o valor e o carimbo de data e hora.
Esta tabela nunca é atualizada nem excluída. Novos fatos são sempre acrescentados como novas linhas.
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');Incorporando eventos a um saldo
Para reconstruir o saldo atual, agregamos todos os eventos. Os depósitos aumentam o saldo, e os saques o reduzem. Uma expressão CASE permite tratar cada tipo de evento com o sinal correto antes da soma.
Essa única consulta fornece o estado atual, derivado inteiramente do registro histórico de eventos.
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;Estado em um instante específico
Um dos aspectos mais poderosos da origem de eventos é a capacidade de reconstruir o estado em qualquer instante. Basta adicionar um filtro WHERE created_at <= :target_time antes da agregação.
Isso fornece uma consulta que permite viajar no tempo, sem exigir alterações adicionais no esquema — o histórico já está no registro de eventos.
-- 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;Saldo acumulado com funções de janela
Em vez de um único total, podemos calcular um saldo acumulado — o saldo depois de cada evento. A função de janela SUM(...) OVER (ORDER BY ...) calcula a soma acumulada à medida que os eventos se acumulam em ordem cronológica.
Isso é extremamente útil para trilhas de auditoria e para depurar transições de estado.
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;Materializando o estado em uma tabela de instantâneos
Reproduzir todos os eventos a cada consulta pode ficar caro à medida que o registro cresce. Uma otimização comum é materializar o estado atual em uma tabela de instantâneos e reconstruí-la periodicamente ou sob demanda.
O instantâneo armazena o resultado agregado; as consultas leem o instantâneo em vez de reproduzir todo o registro sempre.
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;Atualizações incrementais de instantâneos
Quando chegam novos eventos, você não precisa reproduzir todo o histórico. Se tiver registrado o último event_id processado no instantâneo, poderá aplicar apenas a diferença — os eventos que chegaram depois da criação do instantâneo.
Esse padrão incremental mantém as atualizações dos instantâneos rápidas, mesmo em registros grandes.
-- 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;Tabelas temporais e versionamento do sistema
O SQL:2011 introduziu tabelas temporais com versionamento do sistema, que são mantidas pelo próprio banco de dados. Cada linha recebe automaticamente as colunas valid_from e valid_to, gerenciadas pelo mecanismo.
O PostgreSQL não oferece suporte nativo a isso, mas você pode simulá-lo. Outros bancos de dados, como MariaDB e SQL Server, oferecem suporte direto a 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');Consultando o histórico temporal
Com a tabela temporal simulada pronta, você pode perguntar qual era o saldo em qualquer momento do passado filtrando pelo intervalo de validade. A linha cujo intervalo contém o carimbo de data e hora desejado representa o estado naquele momento.
Esse padrão separa a lógica da consulta da reprodução de eventos — a tabela de histórico de estados já contém o resultado agregado.
-- 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';Origem de eventos com várias entidades
Sistemas reais acompanham eventos de muitas entidades ao mesmo tempo. Um registro de eventos compartilhado, com as colunas entity_id e entity_type, permite reconstruir o estado de qualquer objeto a partir de uma única tabela.
Aqui acompanhamos as movimentações de estoque de vários produtos. Reconstruir o estoque atual de cada produto é novamente apenas uma agregação agrupada.
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;Usando CTEs para ganhar clareza
As consultas para reconstruir o estado podem ficar complexas. Envolver a etapa de agregação em uma CTE melhora a legibilidade e permite combinar o estado reconstruído com outras tabelas de forma organizada.
Aqui reconstruímos os saldos das contas e depois os combinamos com uma tabela de referência de contas para incluir os nomes dos proprietários no resultado.
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;Verificação de conhecimento
Avalie sua compreensão sobre como reconstruir estados a partir de eventos em SQL.
Recapitulação da lição
Nesta lição, você aprendeu a reconstruir estados atuais e históricos a partir de um registro imutável de eventos usando SQL.
Principais aprendizados:
- O estado é derivado ao combinar (agregar) eventos com uma expressão
CASEcom sinais dentro deSUM. - Adicionar um filtro de carimbo de data e hora fornece consultas para qualquer instante sem esforço adicional.
- As funções de janela produzem um estado acumulado depois de cada evento.
- As tabelas de instantâneos materializam o resultado agregado para melhorar o desempenho; as atualizações incrementais aplicam apenas os eventos novos.
- As tabelas temporais simuladas armazenam linhas de estado já agregadas, com intervalos de validade para consultas históricas rápidas.
- As CTEs mantêm legíveis as consultas de reconstrução quando você precisa combinar o estado derivado com outras tabelas.
Esses padrões são a base de projetos de bancos de dados orientados a eventos e preparados para auditoria.
Perguntas Frequentes
A aula “Reconstruindo o Estado a Partir de Eventos” é grátis?
Sim — o texto completo de “Reconstruindo o Estado a Partir de Eventos” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de SQL Academy, atualize para CoddyKit PRO. O curso de SQL Academy inclui 4 aulas no total.
O que vou aprender em “Reconstruindo o Estado a Partir de Eventos”?
Combine eventos para obter o estado atual. Você pratica SQL Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar SQL Academy?
Nenhuma experiência prévia é necessária. SQL Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 4 de 4.
Quanto tempo leva a aula “Reconstruindo o Estado a Partir de Eventos”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de SQL Academy?
Sim. Cada aula de SQL Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Por Que Manter o Histórico
- Tabelas de Eventos Somente para Acréscimo
- Linhas Temporais e Versionadas
- Reconstruindo o Estado a Partir de Eventos