A tabela abaixo representa o resultado final do processo de ETL. Cada linha é um cliente único e cada coluna é uma feature construída para modelos de Machine Learning. Para fins de demonstração, são exibidos apenas os 5 primeiros registros, mas a tabela completa contém o perfil de todos os clientes da base de dados. A explicação detalhada de como cada feature foi calculada se encontra nas seções seguintes.
| idCliente | qtdeTransacoesVida | qtdeTransacoes56 | qtdeTransacoes28 | qtdeTransacoes14 | qtdeTransacoes7 | saldoPontos | DiasUltimaInteracao | qtdePontosPosVida | QtdePontosPos56 | QtdePontosPos28 | QtdePontosPos14 | QtdePontosPos7 | qtdePontosNegVida | QtdePontosNeg56 | QtdePontosNeg28 | QtdePontosNeg14 | QtdePontosNeg7 | IdadeBase | produtoVida | produto56 | produto28 | produto14 | produto7 | dtDia | PeriodoMaisTransacao28 | engajamento28Vida |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 000ff655-fa9f-4baa-a108-47f581ec52a1 | 484 | 123 | 84 | 24 | 15 | 22286 | 1.01 | 24286 | 12300 | 8400 | 2400 | 1500 | -2000 | 0 | 0 | 0 | 0 | 600.01 | ChatMessage | Resgatar Ponei | Resgatar Ponei | Resgatar Ponei | Resgatar Ponei | 5 | Tarde | 0.173553719 |
| 001749bd-37b5-4b1e-8111-f9fbba90f530 | 1 | 0 | 0 | 0 | 0 | 50 | 439.01 | 50 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 600.01 | Lista de presença | Lista de presença | Lista de presença | Lista de presença | Lista de presença | -1 | Sem Informação | 0.0 |
| 0019bb9e-26d4-4ebf-8727-fc911ea28a92 | 2 | 0 | 0 | 0 | 0 | 2 | 474.01 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 600.01 | ChatMessage | ChatMessage | ChatMessage | ChatMessage | ChatMessage | -1 | Sem Informação | 0.0 |
| 0033b737-8235-4c0f-9801-dc4ca185af00 | 548 | 1 | 0 | 0 | 0 | 2561 | 50.01 | 2561 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 216.01 | ChatMessage | ChatMessage | ChatMessage | ChatMessage | ChatMessage | -1 | Sem Informação | 0.0 |
| 0097ab76-4637-4ece-8ebc-ab6abd61d662 | 5 | 0 | 0 | 0 | 0 | 152 | 593.01 | 152 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 600.01 | Lista de presença | ChatMessage | ChatMessage | ChatMessage | ChatMessage | -1 | Sem Informação | 0.0 |
Este projeto consiste em um robusto processo de ETL (Extração, Transformação e Carga), desenvolvido inteiramente em SQL, com o objetivo de construir uma Feature Store. A tabela final consolida o perfil comportamental de cada usuário a partir de dados transacionais brutos, servindo como uma base otimizada e fundamental para futuros projetos de Machine Learning.
Cada linha da tabela final representa um cliente único, e as colunas (features) são métricas calculadas que descrevem o engajamento, os hábitos e o histórico do usuário em diferentes janelas de tempo.
O principal valor deste projeto está na transformação de dados brutos em insights acionáveis, demonstrando habilidades avançadas em SQL, engenharia de dados e a lógica por trás da criação de features que potencializam modelos preditivos.
A query foi projetada para calcular 10 features principais que compõem o perfil de cada usuário:
- Quantidade de transações históricas (vida, D7, D14, D28, D56).
- Dias desde a última transação.
- Idade do cliente na base.
- Produto mais utilizado (vida, D7, D14, D28, D56).
- Saldo de pontos atual.
- Pontos acumulados positivos (vida, D7, D14, D28, D56).
- Pontos acumulados negativos (vida, D7, D14, D28, D56).
- Dia da semana mais ativo (últimos 28 dias).
- Período do dia mais ativo (últimos 28 dias).
- Engajamento em D28 versus Vida (proporção de atividade recente).
A estrutura da query utiliza Common Table Expressions (CTEs) com o comando WITH para segmentar o problema em etapas lógicas, melhorando a legibilidade e a manutenção do código.
WITH tb_transacoes AS (
SELECT IdTransacao,
idCliente,
QtdePontos,
datetime(substr(DtCriacao, 1, 19)) AS DtCriacao,
julianday('now') - julianday(substr(DtCriacao, 1, 10)) AS diffDate,
CAST(strftime('%H', substr(DtCriacao, 1, 19)) AS INTEGER) AS dtHora
FROM transacoes
),
Explicação
Esta CTE inicial é a base para quase todos os cálculos. Ela lê a tabela transacoes e cria colunas auxiliares cruciais:
DtCriacao: Padroniza a coluna de data para o formatodatetime.diffDate: Calcula a diferença em dias entre a data atual ('now') e a data de cada transação. É a coluna chave para filtrar as janelas de tempo (D7, D14, D28, D56).dtHora: Extrai apenas a hora de cada transação, essencial para determinar o período do dia (manhã, tarde, noite).
tb_cliente AS (
SELECT idCliente,
datetime(substr(DtCriacao, 1, 19)) AS DtCriacao,
julianday('now') - julianday(substr(DtCriacao, 1, 10)) AS IdadeBase
FROM clientes
),
Explicação
De forma similar à CTE anterior, esta prepara os dados da tabela clientes e calcula a IdadeBase, que representa há quantos dias o cliente se cadastrou na plataforma.
tb_sumario_transacoes AS (
SELECT idCliente,
count(DISTINCT IdTransacao) AS qtdeTransacoesVida,
count(CASE WHEN diffDate <= 56 THEN IdTransacao END) AS qtdeTransacoes56,
count(CASE WHEN diffDate <= 28 THEN IdTransacao END) AS qtdeTransacoes28,
count(CASE WHEN diffDate <= 14 THEN IdTransacao END) AS qtdeTransacoes14,
count(CASE WHEN diffDate <= 7 THEN IdTransacao END) AS qtdeTransacoes7,
sum(qtdePontos) AS saldoPontos,
ROUND(min(diffDate), 2) AS DiasUltimaInteracao,
sum(CASE WHEN qtdePontos > 0 THEN qtdePontos ELSE 0 END) AS qtdePontosPosVida,
sum(CASE WHEN qtdePontos > 0 AND diffDate <= 56 THEN qtdePontos ELSE 0 END) AS QtdePontosPos56,
sum(CASE WHEN qtdePontos > 0 AND diffDate <= 28 THEN qtdePontos ELSE 0 END) AS QtdePontosPos28,
sum(CASE WHEN qtdePontos > 0 AND diffDate <= 14 THEN qtdePontos ELSE 0 END) AS QtdePontosPos14,
sum(CASE WHEN qtdePontos > 0 AND diffDate <= 7 THEN qtdePontos ELSE 0 END) AS QtdePontosPos7,
sum(CASE WHEN qtdePontos < 0 THEN qtdePontos ELSE 0 END) AS qtdePontosNegVida,
sum(CASE WHEN qtdePontos < 0 AND diffDate <= 56 THEN qtdePontos ELSE 0 END) AS QtdePontosNeg56,
sum(CASE WHEN qtdePontos < 0 AND diffDate <= 28 THEN qtdePontos ELSE 0 END) AS QtdePontosNeg28,
sum(CASE WHEN qtdePontos < 0 AND diffDate <= 14 THEN qtdePontos ELSE 0 END) AS QtdePontosNeg14,
sum(CASE WHEN qtdePontos < 0 AND diffDate <= 7 THEN qtdePontos ELSE 0 END) AS QtdePontosNeg7
FROM tb_transacoes
GROUP BY idCliente
),
Explicação
Esta é a CTE principal de agregação, onde a maioria das features é calculada. Ela agrupa os dados por idCliente para gerar um resumo único para cada usuário:
- Quantidade de Transações: Utiliza
COUNT(CASE WHEN ...)para contar transações dentro de cada janela de tempo. - Saldo de Pontos: Um
SUM()simples naqtdePontoscalcula o saldo total. - Dias Última Interação: Usa
MIN(diffDate)para encontrar a transação mais recente (a menor diferença de dias). - Pontos Positivos e Negativos: Utiliza
SUM(CASE WHEN ...)para somar separadamente os pontos ganhos e os pontos gastos.
Para encontrar o produto mais usado, o problema foi dividido em três CTEs:
-- CTE 1: Junção de Transações e Produtos tb_transacao_produto AS ( SELECT t1.*, t2.IdProduto, t3.DescNomeProduto, t3.DescCategoriaProduto FROM tb_transacoes AS t1 LEFT JOIN transacao_produto AS t2 ON t1.IdTransacao = t2.IdTransacao LEFT JOIN produtos AS t3 ON t2.IdProduto = t3.IdProduto ),-- CTE 2: Contagem de Produtos por Cliente e Janela de Tempo tb_cliente_produto AS ( SELECT idCliente, DescNomeProduto, count(*) AS QtdeVida, count( CASE WHEN diffDate <= 56 THEN IdTransacao END) AS qtde56, count( CASE WHEN diffDate <= 28 THEN IdTransacao END) AS qtde28, count( CASE WHEN diffDate <= 14 THEN IdTransacao END) AS qtde14, count( CASE WHEN diffDate <= 7 THEN IdTransacao END) AS qtde7 FROM tb_transacao_produto GROUP BY idCliente, DescNomeProduto ),
-- CTE 3: Ranqueamento dos Produtos tb_cliente_produto_rn AS ( SELECT *, row_number() OVER (PARTITION BY idCliente ORDER BY QtdeVida DESC) AS rnVida, row_number() OVER (PARTITION BY idCliente ORDER BY Qtde56 DESC) AS rn56, row_number() OVER (PARTITION BY idCliente ORDER BY Qtde28 DESC) AS rn28, row_number() OVER (PARTITION BY idCliente ORDER BY Qtde14 DESC) AS rn14, row_number() OVER (PARTITION BY idCliente ORDER BY Qtde7 DESC) AS rn7 FROM tb_cliente_produto ),
Explicação
tb_transacao_produto: Enriquece a tabela de transações com o nome do produto correspondente.tb_cliente_produto: Agrupa por cliente e produto para contar a frequência de uso em cada janela de tempo.tb_cliente_produto_rn: Utiliza a função de janelaROW_NUMBER()para criar um ranking. O produto com ranking 1 (rnVida = 1,rn56 = 1, etc.) é o mais usado em cada período.
A mesma técnica de ranqueamento foi aplicada para encontrar o dia da semana e o período do dia mais ativos.
-- Dia da semana mais ativo tb_cliente_dia AS ( SELECT IdCliente, strftime('%w', DtCriacao) AS dtDia, count(*) AS QtdeTransacao FROM tb_transacoes WHERE diffDate <= 28 GROUP BY idCliente, dtDia ), tb_cliente_dia_rn AS ( SELECT *, row_number() OVER (PARTITION BY idCliente ORDER BY QtdeTransacao DESC) AS rnDia FROM tb_cliente_dia ),
-- Período do dia mais ativo tb_cliente_periodo AS ( SELECT idCliente, CASE WHEN dtHora BETWEEN 7 AND 12 THEN 'Manhã' WHEN dtHora BETWEEN 13 AND 18 THEN 'Tarde' WHEN dtHora BETWEEN 19 AND 23 THEN 'Noite' ELSE 'Madrugada' END AS periodo, count(*) AS QtdeTransacao FROM tb_transacoes WHERE diffDate <= 28 GROUP BY 1, 2 ), tb_cliente_periodo_rn AS ( SELECT *, row_number() OVER ( PARTITION BY idCliente ORDER BY QtdeTransacao DESC) AS rnPeriodo FROM tb_cliente_periodo ),
Explicação
tb_cliente_dia_rn: Filtra os últimos 28 dias, conta as transações por dia da semana e usaROW_NUMBER()para encontrar o dia com mais atividade (rnDia = 1).tb_cliente_periodo_rn: Filtra os últimos 28 dias, classifica as transações em "Manhã", "Tarde", "Noite" ou "Madrugada", e usaROW_NUMBER()para encontrar o período mais ativo (rnPeriodo = 1).
tb_join AS (
SELECT t1.*,
ROUND(t2.IdadeBase, 2) AS IdadeBase,
t3.DescNomeProduto AS produtoVida,
t4.DescNomeProduto AS produto56,
t5.DescNomeProduto AS produto28,
t6.DescNomeProduto AS produto14,
t7.DescNomeProduto AS produto7,
COALESCE(t8.dtDia, -1) AS dtDia,
COALESCE(t9.periodo, 'Sem Informação') AS PeriodoMaisTransacao28
FROM tb_sumario_transacoes AS t1
LEFT JOIN tb_cliente AS t2 ON t1.idCliente = t2.idCliente
LEFT JOIN tb_cliente_produto_rn AS t3 ON t1.idCliente = t3.idCliente AND t3.rnVida = 1
LEFT JOIN tb_cliente_produto_rn AS t4 ON t1.idCliente = t4.idCliente AND t4.rn56 = 1
LEFT JOIN tb_cliente_produto_rn AS t5 ON t1.idCliente = t5.idCliente AND t5.rn28 = 1
LEFT JOIN tb_cliente_produto_rn AS t6 ON t1.idCliente = t6.idCliente AND t6.rn14 = 1
LEFT JOIN tb_cliente_produto_rn AS t7 ON t1.idCliente = t7.idCliente AND t7.rn7 = 1
LEFT JOIN tb_cliente_dia_rn AS t8 ON t1.idCliente = t8.idCliente AND rnDia = 1
LEFT JOIN tb_cliente_periodo_rn AS t9 ON t1.idCliente = t9.idCliente AND rnPeriodo = 1
)
Explicação
Esta CTE é a etapa de montagem final. Ela une todos os resultados parciais:
- Inicia com
tb_sumario_transacoescomo a base. - Usa
LEFT JOINpara adicionar aIdadeBase. - O passo mais importante é a junção com as tabelas de ranking. A condição
AND rn... = 1garante que apenas a linha correspondente ao item mais frequente (produto, dia ou período) seja unida, mantendo a granularidade de um cliente por linha. COALESCEé usado para tratar valores nulos, atribuindo um valor padrão caso não haja dados.
CREATE TABLE tb_projeto AS
SELECT *,
1.0 * qtdeTransacoes28 / qtdeTransacoesVida AS engajamento28Vida
FROM tb_join
Explicação
Finalmente, a consulta externa seleciona todos os dados da tb_join e calcula a última feature:
engajamento28Vida: Uma métrica que calcula a proporção de transações recentes (D28) em relação ao total, indicando o nível de engajamento atual do usuário.
O CREATE TABLE tb_projeto AS armazena o resultado completo desta query em uma nova tabela permanente, finalizando o processo ETL.
- Linguagem: SQL (SQLite Dialect)
- Conceitos: ETL, Engenharia de Features, Funções de Janela (Window Functions), CTEs, Agregação Condicional.