← SQL: perguntas melhores aos dados
07 / 11 · 60 MIN

Funções de janela e séries operacionais

Preserva detalhe, escolhe rankings, define frames e verifica a população antes de interpretar um indicador.

Escolher a unidade de cada linha

Antes de escrever uma função, define o que representa uma linha do resultado: movimento, conta, dia ou incidente. Uma janela acrescenta um cálculo ao detalhe que já existe. GROUP BY reduz linhas à granularidade do grupo. Num relatório fictício, uma conta com movimentos 7 e 5 pode aparecer duas vezes com total 12 em cada linha; isso é correto para detalhe enriquecido, mas não para uma lista de totais por conta. Escreve a contagem esperada de linhas antes de executar. Num ambiente com vários tenants, a identidade pode incluir tenant_id e account_id. Corrigir a partição evita misturar totais, mas não concede autorização para ler dados de outro tenant.

Empates, seleção e apresentação

ROW_NUMBER é útil para escolher uma linha quando existe um critério completo de desempate. Se a regra pede o evento mais recente e depois o maior ID, ordena por timestamp DESC e ID DESC. RANK e DENSE_RANK respondem a outra necessidade: conservar grupos empatados. Para valores 90,90,40, RANK dá 1,1,3 e DENSE_RANK dá 1,1,2. Se queres os dois maiores valores distintos, a segunda sequência exprime o contrato. Não acrescentes um ID único à ordem do ranking quando isso destruiria os empates pretendidos. Usa um ORDER BY exterior para apresentar os incidentes numa ordem estável, mantendo separadas a regra de seleção e a apresentação.

O frame define o acumulado

Uma soma ordenada não significa necessariamente uma soma linha a linha. Em PostgreSQL, o frame predefinido com ORDER BY inclui os pares do valor de ordenação atual. No exemplo executável, dois movimentos partilham o instante 10: ambos recebem 40 na coluna peer_total. Para um percurso por movimento, acrescenta o ID à ordem e define ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW; obténs 20,40,45. O desempate precisa de corresponder ao contrato, não apenas a uma coluna disponível. LAST_VALUE também considera o fim do frame. Se precisas do último valor da partição em todas as linhas, explicita um frame que termine em UNBOUNDED FOLLOWING.

Séries esparsas e valores desconhecidos

LAG procura uma posição anterior na sequência de linhas, não uma data ausente. Se há observações nos dias 1 e 4, a linha anterior ao dia 4 pode ser a do dia 1. Uma média de três linhas também não corresponde automaticamente a três dias de calendário. Para um indicador diário, constrói a população temporal pretendida e junta os dados existentes. Só substituas ausência por zero quando a definição da métrica o permitir: dia sem movimento é diferente de falha de recolha. Em PostgreSQL 18, LAG não salta automaticamente valores NULL. Uma linha anterior com NULL continua a representar informação desconhecida que a regra de análise precisa de tratar.

Colocar o filtro no nível certo

A população vista pela janela já foi filtrada pelo WHERE desse nível. Se valores 40 e 60 representam a população e reténs apenas valores acima de 50 antes de calcular a soma, a percentagem de 60 passa a ser 100%. Para comparar com o total original, calcula primeiro o denominador e filtra numa consulta exterior. A mesma distinção aparece num painel de incidentes: último evento open não é igual a último evento cujo estado é open. Filtrar open antes de escolher o último evento pode ressuscitar um incidente já resolvido. Desenha duas etapas e escreve que população cada uma deve conservar antes de escolher a sintaxe.

Laboratório: provar forma e valores

Executa o exemplo numa base de aprendizagem e compara quatro propriedades: três linhas conservadas, totais com pares 40,40,45, acumulado por movimento 20,40,45 e ordem final por ID. Depois muda o valor da segunda linha para 8 e prevê o resultado antes de repetir. Acrescenta uma segunda conta e verifica se é necessário PARTITION BY; não assumes que um exemplo com uma conta prova isolamento entre contas. Para um saldo inicial, soma o acumulado dos movimentos ao saldo uma vez em cada resultado, sem transformar o saldo inicial num movimento repetido. Termina por explicar a diferença entre linha, partição, frame e ordem de apresentação a alguém da equipa.

WITH movements(id, instant, amount) AS (
  VALUES (1,10,20), (2,10,20), (3,11,5)
)
SELECT id,
       SUM(amount) OVER (ORDER BY instant) AS peer_total,
       SUM(amount) OVER (
         ORDER BY instant, id
         ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
       ) AS movement_total
FROM movements
ORDER BY id;
NA PRÁTICA

Três movimentos produzem peer_total 40,40,45 e movement_total 20,40,45. A diferença é explicada pelo frame e pelo desempate, não por alterações dos montantes.

Armadilhas comuns

Usar a ordem da janela como ordem final, acrescentar ID a um ranking que deve conservar empates ou tratar uma linha anterior como um dia anterior.

Tópicos relacionados: Juntar sem multiplicar enganos · Contar e agrupar com cuidado · Índices e planos de execução

Leva esta ideia contigo

Define primeiro a linha, a população e o desempate; depois escolhe a janela e o frame que reproduzem esse contrato.

Criar conta

Referência: PostgreSQL 18: Window Functions · PostgreSQL 18 reference semantics; DR SQL 2026.4; synthetic plan metrics and portable SQLite 3.51.2 examples