sqlpostgresqlwindow-functionsanalytics

WIDTH_BUCKET no SQL: histogramas de largura fixa e distribuicoes

Como o WIDTH_BUCKET distribui valores em faixas de largura fixa, trata os limites e gera distribuicoes com GROUP BY.

3 min de leituraReferencesql · postgresql · window-functions · analytics · histogram

WIDTH_BUCKET responde a uma pergunta simples: em qual faixa do histograma um numero cai se dividirmos um intervalo em partes de largura igual. E a ferramenta certa sempre que voce precisa agrupar um valor continuo (valor do pedido, salario, pontuacao) em baldes uniformes e montar uma distribuicao.

Assinatura e logica

A chamada canonica no PostgreSQL recebe quatro argumentos:

SELECT WIDTH_BUCKET(score, 0, 100, 10) AS bucket;  -- bucket 1..10

Isso significa: pegue o intervalo de 0 a 100, corte em 10 partes iguais de largura 10 cada uma e retorne o numero da faixa para score. Um valor em 0..10 cai no balde 1, 10..20 no balde 2, e assim por diante. O limite inferior e inclusivo e o superior e exclusivo.

O recurso central sao os dois baldes extras:

  • se o valor for menor que o limite inferior, voce recebe 0 (underflow);
  • se o valor for igual ou maior que o limite superior, voce recebe n + 1 (overflow).

Voce nunca perde linhas em silencio: tudo que fica fora do intervalo se acumula direitinho nas bordas.

Distribuicoes com GROUP BY

O uso mais comum e um histograma dos valores de pedido. Divida amount de 0 a 1000 em 10 baldes e conte cada um:

SELECT
  WIDTH_BUCKET(amount, 0, 1000, 10) AS bucket,
  COUNT(*) AS orders,
  ROUND(MIN(amount), 2) AS lo,
  ROUND(MAX(amount), 2) AS hi
FROM orders
WHERE status = 'paid'
GROUP BY bucket
ORDER BY bucket;

O balde 0 junta estornos e valores zerados, e o balde 11 junta os pedidos gigantes acima de 1000. Para transformar o numero em um rotulo legivel, calcule os limites dentro da propria consulta:

SELECT
  bucket,
  (bucket - 1) * 100 AS range_lo,
  bucket * 100       AS range_hi,
  COUNT(*)           AS orders
FROM (
  SELECT WIDTH_BUCKET(amount, 0, 1000, 10) AS bucket
  FROM orders
  WHERE status = 'paid'
) t
GROUP BY bucket
ORDER BY bucket;

O mesmo padrao serve para salarios: distribua employees.salary em faixas e veja na hora onde fica a maior parte do quadro de pessoal.

SELECT
  WIDTH_BUCKET(salary, 30000, 150000, 6) AS band,
  dept,
  COUNT(*) AS headcount
FROM employees
GROUP BY band, dept
ORDER BY band, dept;

WIDTH_BUCKET versus NTILE

As duas funcoes sao faceis de confundir, mas dividem os dados em eixos diferentes:

  • WIDTH_BUCKET corta o eixo de valores em segmentos de largura igual. Os baldes podem ficar vazios ou cheios demais, e isso e normal, porque o formato da distribuicao e exatamente o que voce quer enxergar.
  • NTILE(n) corta o conjunto de linhas em n grupos de tamanho parecido (quantis). A largura de cada intervalo varia, mas cada grupo guarda quase a mesma quantidade de linhas.

Use WIDTH_BUCKET quando os limites fixos importam ("pedidos 0-100, 100-200..."). Use NTILE quando precisa de percentis ("os 10% de clientes que mais gastam"):

SELECT
  user_id,
  SUM(amount) AS lifetime,
  NTILE(4) OVER (ORDER BY SUM(amount)) AS quartile
FROM orders
GROUP BY user_id;

Pegadinha: limites e NULL

Algumas armadilhas em que e facil escorregar:

  • O limite superior e exclusivo. Exatamente WIDTH_BUCKET(100, 0, 100, 10) retorna 11, e nao 10. Para puxar o maximo para o ultimo balde real, amplie o limite superior ou use LEAST(amount, 999.99).
  • Uma entrada NULL gera uma saida NULL, entao essas linhas somem da distribuicao. Filtre-as explicitamente ou incorpore-as com COALESCE.
  • Voce pode passar os limites invertidos (superior < inferior), o que inverte a numeracao e faz os baldes contarem para tras. Isso raramente e o que voce quer, entao confira bem a ordem.

Diferencas entre os bancos

  • PostgreSQL e Oracle suportam diretamente a forma de quatro argumentos do WIDTH_BUCKET.
  • MySQL nao tem WIDTH_BUCKET. Emule com aritmetica: FLOOR((amount - 0) / 100) + 1, e trate underflow e overflow na mao.
  • ClickHouse oferece roundDown(amount, [0, 100, 200, ...]) e o simples floor(amount / 100) para o mesmo efeito.

A regra de ouro: fixe min, max e a quantidade de baldes como limites de negocio deliberados em vez de ajusta-los aos dados atuais, senao dois relatorios vizinhos deixam de ser comparaveis.

Pratique com exercícios reais

Resolva exercícios no treinador de SQL com correção instantânea e dicas.

Abrir o treinador