sqlpostgresqlntilewindow-functions

NTILE no SQL: quartis, decis e coortes em baldes iguais

Como NTILE(n) divide linhas ordenadas em n baldes quase iguais, para onde vai o resto e como difere de WIDTH_BUCKET.

2 min de leituraReferencesql · postgresql · ntile · window-functions · analytics

NTILE(n) e uma funcao de janela que divide um conjunto ordenado de linhas em n baldes o mais iguais possivel em tamanho e marca cada linha com um numero de balde de 1 a n. E o cavalo de batalha por tras de quartis, decis e qualquer coorte de "numero igual de clientes por grupo".

O caso basico: quartis

Vamos dividir os usuarios em quatro grupos iguais pelo gasto total. ORDER BY ... DESC significa que o balde 1 e o "topo".

SELECT
  u.id,
  u.email,
  SUM(o.amount) AS spent,
  NTILE(4) OVER (ORDER BY SUM(o.amount) DESC) AS quartile
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE o.status = 'paid'
GROUP BY u.id, u.email;

A diferenca chave em relacao a RANK/ROW_NUMBER: o NTILE olha para o numero de linhas, nao para os valores concretos. Dez linhas com NTILE(4) produzem baldes de tamanho 3, 3, 2, 2 — a funcao arruma as linhas para voce.

Como o resto e distribuido

Quando o numero de linhas nao divide exato por n, as linhas extras vao para os baldes de numero menor. Para 10 linhas e NTILE(4): o resto e 10 mod 4 = 2, entao os dois primeiros baldes recebem uma linha extra cada.

  • 10 linhas, NTILE(4) → 3, 3, 2, 2
  • 11 linhas, NTILE(4) → 3, 3, 3, 2
  • 7 linhas, NTILE(3) → 3, 2, 2

Pegadinha: quando n e maior que o numero de linhas, alguns baldes nunca aparecem. Sete linhas com NTILE(10) produzem numeros de 1 a 7, e os baldes 8, 9, 10 ficam vazios. Nao assuma que todos os baldes existem.

Uma segunda armadilha: valores iguais (empates) ainda podem cair em baldes diferentes, porque se contam linhas, nao valores. Se o limite de uma coorte precisa coincidir com um limite de valor, o NTILE e a ferramenta errada — use WIDTH_BUCKET ou um CASE manual.

PARTITION BY: decis dentro de cada pais

PARTITION BY recalcula os baldes de forma independente dentro de cada grupo. Abaixo, decis de gasto calculados por pais.

SELECT
  u.country,
  u.email,
  SUM(o.amount) AS spent,
  NTILE(10) OVER (
    PARTITION BY u.country
    ORDER BY SUM(o.amount) DESC
  ) AS decile
FROM users u
JOIN orders o ON o.user_id = u.id
GROUP BY u.country, u.email;

Cada pais ganha sua propria escada de 1 a 10. A mesma ideia da decis de salario por departamento em uma unica consulta:

SELECT name, dept, salary,
  NTILE(4) OVER (PARTITION BY dept ORDER BY salary DESC) AS pay_quartile
FROM employees;

NTILE versus WIDTH_BUCKET

E facil confundir os dois, mas a logica e diferente:

  • NTILE(n) iguala o numero de linhas entre os baldes. Cada balde tem mais ou menos o mesmo tamanho, mas as faixas de valor flutuam.
  • WIDTH_BUCKET(val, lo, hi, n) iguala a largura da faixa. Os baldes cobrem larguras de valor iguais, mas a contagem de linhas pode variar muito.
-- Faixas de mesma LARGURA: 0-250, 250-500, 500-750, 750-1000
SELECT id, amount,
  WIDTH_BUCKET(amount, 0, 1000, 4) AS price_band
FROM orders;

Regra pratica: "numero igual de clientes por grupo" → NTILE; "faixas de preco fixas" → WIDTH_BUCKET.

Compatibilidade

  • PostgreSQL — suporta totalmente tanto NTILE quanto WIDTH_BUCKET.
  • MySQL — NTILE existe desde a 8.0; nao ha WIDTH_BUCKET, entao ele e emulado com uma expressao FLOOR(...).
  • ClickHouse — nao ha NTILE nativo; use intDiv(rowNumberInAllBlocks(), ...) ou aritmetica sobre row_number().

O que importa lembrar: o NTILE corta por linhas, e o resto sempre pende para os baldes de numero menor — e isso que faz as coortes por quartis serem honestas.

Pratique com exercícios reais

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

Abrir o treinador