Como criar uma tabela de histórico de preços em Data Warehouse: passo a passo
Este tutorial mostra como criar uma tabela de histórico de preços em Data Warehouse para registar alterações de preço de produtos ao longo do tempo. Ter um histórico de preços permite análises temporais, calcular tendências e reconstruir preços válidos para qualquer data.
Pré-requisitos
- Conhecimentos básicos de SQL (SELECT, INSERT, UPDATE)
- Ambiente Data Warehouse com suporte a SQL (ex.: Azure Synapse, SQL Server, Snowflake)
- Tabela de origem de produtos com identificador e preço actual
- Permissões para criar tabelas e triggers/procedures (ou processos ETL)
Passo 1: Escolher o modelo de histórico (SCD vs timeline)
Há duas abordagens comuns: usar SCD Tipo 2 (registos com date_effective/date_end e flag activo) ou manter uma tabela de eventos de preço (cada alteração é um evento). Para histórico de preços simples e consultas por data, o padrão SCD Tipo 2 ou uma timeline com date_effective é ideal. Aqui usamos uma timeline com date_effective e date_end para consultas fáceis.
Passo 2: Criar a tabela de histórico de preços
Criar a tabela que guardará versões de preço. Mantemos campos essenciais: product_id, price, currency, date_effective (início da validade), date_end (fim da validade, NULL se actual), is_current.
CREATE TABLE price_history (
price_history_id BIGINT IDENTITY(1,1) PRIMARY KEY,
product_id INT NOT NULL,
price DECIMAL(18,4) NOT NULL,
currency VARCHAR(3) NOT NULL DEFAULT 'EUR',
date_effective DATE NOT NULL,
date_end DATE NULL,
is_current BIT NOT NULL DEFAULT 1,
created_at DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME()
);
Passo 3: Popular a tabela inicial a partir da tabela de produtos
Copie o estado actual dos preços para a price_history com date_effective igual à data de carga. Este passo garante que existe pelo menos um registo por produto.
INSERT INTO price_history (product_id, price, currency, date_effective, date_end, is_current)
SELECT p.product_id, p.price, p.currency, CAST(GETDATE() AS DATE), NULL, 1
FROM products p
WHERE p.price IS NOT NULL;
Passo 4: Processar alterações de preço (ETL ou trigger)
Quando chega uma nova carga de preços (feed diário ou evento), compare com price_history e feche registos antigos antes de inserir o novo. Abaixo um exemplo ETL em SQL que processa a tabela staging_prices com os preços mais recentes.
-- 1) Encontrar produtos com mudança de preço
WITH changes AS (
SELECT s.product_id, s.price, s.currency
FROM staging_prices s
JOIN price_history ph ON ph.product_id = s.product_id AND ph.is_current = 1
WHERE ph.price <> s.price OR ph.currency <> s.currency
)
-- 2) Fecha os registos actuais, definindo date_end e is_current = 0
UPDATE ph
SET ph.date_end = CAST(GETDATE() AS DATE), ph.is_current = 0
FROM price_history ph
JOIN changes c ON ph.product_id = c.product_id
WHERE ph.is_current = 1;
-- 3) Insere novos registos com a nova validade
INSERT INTO price_history (product_id, price, currency, date_effective, date_end, is_current)
SELECT c.product_id, c.price, c.currency, CAST(GETDATE() AS DATE), NULL, 1
FROM changes c;
Passo 5: Consultas comuns e optimizações
Consulta do preço válido numa data, agregações de tendência e índices para performance. Crie índice por (product_id, is_current) e por (product_id, date_effective, date_end) se houver muitas pesquisas históricas.
-- Preço actual por produto
SELECT product_id, price
FROM price_history
WHERE product_id = 123 AND is_current = 1;
-- Preço válido numa data específica
SELECT price
FROM price_history
WHERE product_id = 123
AND date_effective <= '2026-10-01'
AND (date_end IS NULL OR date_end >= '2026-10-01');
-- Índice sugerido (SQL Server example)
CREATE INDEX IX_price_history_product_current ON price_history(product_id, is_current);
Verificar o resultado
Confirme que cada produto tem exactamente um registo com is_current = 1 (se existir preço actual) e que registos antigos têm date_end preenchido. Execute amostras de consultas por data para validar valores históricos. Exemplos:
-- Verificar unicity current
SELECT product_id, COUNT(*) as cnt
FROM price_history
WHERE is_current = 1
GROUP BY product_id
HAVING COUNT(*) > 1;
-- Verificar produtos sem registo
SELECT p.product_id
FROM products p
LEFT JOIN price_history ph ON p.product_id = ph.product_id
WHERE ph.price_history_id IS NULL;
Conclusão
Uma tabela de histórico de preços bem concebida permite análises temporais e consistência nos relatórios. Próximos passos: automatizar o processo ETL, tratar fusões de produtos e gerir regras de moeda. Dica: testar o fluxo com dados reais e verificar edge cases como alterações múltiplas no mesmo dia.