(+351) 21 24 10006  ·  info@bconcepts.pt
Carnaxide, Lisboa

Como criar pipelines ELT para anonimizar PII: passo a passo

João Barros 15 de August de 2026 3 min de leitura

Aprende a anonimizar PII em ELT de forma prática e segura: este tutorial descreve por que é importante tratar PII (Personally Identifiable Information), que técnicas usar e como integrar essas transformações num pipeline ELT para manter os dados úteis para análises sem expor informações sensíveis. Vou explicar o porquê de cada escolha e dar passos concretos com exemplos SQL que funcionam em motores compatíveis (Databricks, Synapse, Snowflake).

Pré-requisitos

  • Conta ou ambiente com um motor SQL/Delta compatível (ex.: Databricks, Synapse, Snowflake). Idealmente com 4–8 nós para testar performance em conjuntos de 1–10M registos.
  • Fonte de dados com PII (CSV/JSON) acessível ao ambiente; um ficheiro de teste com ~1000 linhas ajuda a validar antes do rollout.
  • Permissões para criar tabelas, funções e executar jobs ELT; acesso a um serviço de tokenização se necessidade de reversão existir.
  • Noções básicas de SQL e funções de hashing/cryptography. Conhecimentos de MERGE/UPSERT e particionamento são úteis para pipelines idempotentes.

Passo 1: Identificar campos PII e definir requisitos de anonimização

Antes de codificar, faz um inventário dos campos PII (ex.: name, email, NIF, telemóvel). Para cada campo decide o método adequado: mascaramento simples (mantém parte legível), hashing irreversível (SHA256) quando não precisas de reverter, tokenização quando precisa de reversão controlada, ou generalização quando apenas agregação é necessária (ex.: armazenar apenas prefixo de telemóvel).

Exemplo de mapeamento para 5 campos:

  • name: mascaramento (primeiros 2 caracteres) — preserva segmentação por iniciais em relatórios;
  • email: manter domínio, hashar o user — útil para manter métricas por domínio (ex.: gmail.com) sem expor user;
  • NIF: hashing irreversível — obrigatório para impedir reconstrução;
  • telemóvel: generalização para código nacional (3 dígitos) — suficiente para análises por região;
  • created_at: manter intacto para análises temporais.
Definir requisitos legais também: por exemplo, a retenção de PII pode estar limitada a 2 anos — documenta isto no plano de governação.

Passo 2: Criar tabela staging non-anonymized

Carrega os dados originais para uma tabela staging sem alterações. Isto permite auditoria, reconciliação e reprocessamento. Mantém o staging numa zona segura (ex.: OneLake/secure storage) com controlo de acesso restrito à equipa responsável.

-- Exemplo SQL para Databricks/Synapse/Snowflake
CREATE OR REPLACE TABLE raw_customers_staging (
  id STRING,
  name STRING,
  email STRING,
  nif STRING,
  phone STRING,
  created_at TIMESTAMP
);

-- COPY/LOAD a partir de CSV ou caminho de origem (varia por plataforma)
-- Para grandes volumes recomenda-se particionar por created_at ou por dia.

Passo 3: Implementar funções de anonimização reutilizáveis

Cria UDFs/SQL functions para que a lógica seja centralizada, testável e auditável. Isto facilita updates (ex.: mudar algoritmo de hashing) sem alterar múltiplas queries. Testa as funções com conjuntos de 1000–10.000 registos antes de usar em produção.

-- Hash irreversível (SHA256) e truncar para uma coluna identificadora
CREATE OR REPLACE FUNCTION hash_sha256(val STRING) RETURNS STRING AS (
  sha2(val, 256)
);

-- Mascaramento: manter primeiros 2 caracteres e substituir o resto por X
CREATE OR REPLACE FUNCTION mask_name(val STRING) RETURNS STRING AS (
  CASE WHEN val IS NULL THEN NULL
       WHEN length(val) <= 2 THEN repeat('X', length(val))
       ELSE substr(val,1,2) || repeat('X', length(val)-2)
  END
);

-- Generalizar telemóvel: manter só prefixo nacional (ex.: primeiros 3 dígitos)
CREATE OR REPLACE FUNCTION generalize_phone(val STRING) RETURNS STRING AS (
  CASE WHEN val IS NULL THEN NULL
       WHEN length(regexp_replace(val,'\\D','')) <= 3 THEN 'REDACTED'
       ELSE substr(regexp_replace(val,'\\D',''),1,3) || 'XXXXX'
  END
);

Passo 4: Anonimizar com uma query ELT e gravar em tabela target

Aplica as funções na transformação e escreve o resultado para a tabela final que será usada por relatórios. Mantém colunas não sensíveis intactas e documenta claramente quais campos foram transformados. Se precisares de reverter, integra um serviço de tokenização (gerido e auditado) em vez de hashing.

CREATE OR REPLACE TABLE customers_anonymized AS
SELECT
  id,
  mask_name(name) AS name_masked,
  -- manter domínio: split email e hash o user
  concat(hash_sha256(split_part(email,'@',1)), '@', split_part(email,'@',2)) AS email_anonymized,
  -- NIF hashed para irreversibilidade
  hash_sha256(nif) AS nif_hash,
  generalize_phone(phone) AS phone_generalized,
  created_at
FROM raw_customers_staging;

Passo 5: Automatizar ELT e validar idempotência

Programa o job para correr periodicamente (ex.: cada hora ou nightly). Garante idempotência usando MERGE/UPSERT e watermarking (por exemplo, processar apenas records com created_at >= ultima_run). Testa com reruns: ao recriar pipeline 3 vezes seguidas o número de registos na tabela target deve permanecer estável.

-- Exemplo MERGE para atualização incremental
MERGE INTO customers_anonymized tgt
USING (SELECT * FROM raw_customers_staging WHERE created_at >= date_sub(current_date(),1)) src
ON tgt.id = src.id
WHEN MATCHED THEN UPDATE SET
  name_masked = mask_name(src.name),
  email_anonymized = concat(hash_sha256(split_part(src.email,'@',1)), '@', split_part(src.email,'@',2)),
  nif_hash = hash_sha256(src.nif),
  phone_generalized = generalize_phone(src.phone),
  created_at = src.created_at
WHEN NOT MATCHED THEN INSERT VALUES (
  src.id,
  mask_name(src.name),
  concat(hash_sha256(split_part(src.email,'@',1)), '@', split_part(src.email,'@',2)),
  hash_sha256(src.nif),
  generalize_phone(src.phone),
  src.created_at
);

Verificar o resultado

Faz verificações automáticas e manuais: assegura que não existem campos originais na tabela anonymizada, que formatos são consistentes e que o hashing tem o comprimento esperado (SHA256 → 64 hex chars). Exemplos de queries de validação e métricas:

-- Amostra de 5 linhas para inspeção visual
SELECT id, name_masked, email_anonymized, phone_generalized FROM customers_anonymized LIMIT 5;

-- Verifica que nif_hash tem comprimento 64 (SHA256 hex)
SELECT DISTINCT length(nif_hash) AS len FROM customers_anonymized LIMIT 5;

-- Conta linhas para garantir idempotência após rerun
SELECT count(*) FROM customers_anonymized;

-- Percentagem de NULLs por coluna para validar qualidade
SELECT
  sum(CASE WHEN name_masked IS NULL THEN 1 ELSE 0 END)/count(*) AS pct_null_name,
  sum(CASE WHEN email_anonymized IS NULL THEN 1 ELSE 0 END)/count(*) AS pct_null_email
FROM customers_anonymized;

Conclusão

Anonimizar PII em ELT reduz risco e permite análises úteis. Começa pequeno (ex.: 10k registos) para validar corretude e performance antes de escalar para milhões de linhas. Próximos passos práticos: implementar tokenização reversível se necessário, adicionar logging e auditoria (quem processou o job, quando), e testar a performance com dados em batch e streaming. E, claro, confirma requisitos legais e políticas internas de governação antes do roll-out.