sqlpostgresqlaggregationanalytics

PERCENTILE_DISC no SQL: percentis discretos sem interpolacao

Como PERCENTILE_DISC retorna um valor real dos seus dados sem interpolacao, como difere de PERCENTILE_CONT e quando escolher a variante discreta.

2 min de leituraReferencesql · postgresql · aggregation · analytics · statistics

PERCENTILE_DISC retorna um percentil como um dos valores que realmente existem nos seus dados, sem interpolacao entre vizinhos. Isso o torna a escolha certa quando "a media de duas linhas" nao faz sentido: para categorias, precos discretos e qualquer caso em que o resultado precise ser um valor real da tabela.

Sintaxe basica

PERCENTILE_DISC e uma agregacao de conjunto ordenado (ordered-set aggregate), por isso usa a sintaxe especial WITHIN GROUP (ORDER BY ...). O argumento entre parenteses e uma fracao entre 0 e 1.

-- Median order amount as a real value from the data
SELECT PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY amount) AS median_amount
FROM orders;

-- 90th percentile of salaries
SELECT PERCENTILE_DISC(0.9) WITHIN GROUP (ORDER BY salary) AS p90_salary
FROM employees;

Como o valor e escolhido: as linhas sao ordenadas pela clausula ORDER BY e entao retorna-se o primeiro valor cuja fracao acumulada de linhas atinge o alvo. O resultado e sempre igual a um dos valores da coluna.

Discreto versus continuo

PERCENTILE_CONT interpola: com um numero par de linhas a mediana e a media das duas centrais. PERCENTILE_DISC nao interpola; ele devolve um valor real. Compare os dois em uma unica consulta:

-- For amounts 100, 200, 300, 400:
-- CONT median = 250 (interpolated), DISC median = 200 (real value)
SELECT
  PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY amount) AS disc_median,
  PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount) AS cont_median
FROM orders;

Diferencas principais:

  • DISC sempre retorna um valor presente nos dados; CONT pode retornar um numero que nao esta na tabela.
  • CONT exige um tipo numerico (ou intervalo). DISC tambem funciona com texto, datas e enums, qualquer tipo ordenavel.
  • Em amostras grandes os resultados convergem, mas em pequenas divergem de forma notavel.

Quando escolher o discreto

Use PERCENTILE_DISC quando um valor intermediario nao faz sentido ou e invalido:

  • Dados categoricos e de texto: um status ou pais "mediano" precisa ser uma string real, nao uma invencao.
  • Valores e precos discretos onde nao pode existir "250.5".
  • SLA e relatorios onde voce precisa exibir um valor concreto e observado.
-- Median country code per status: must be a real value, not interpolated
SELECT o.status,
       PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY u.country) AS median_country
FROM orders o
JOIN users u ON u.id = o.user_id
GROUP BY o.status;

Aqui PERCENTILE_CONT simplesmente nao roda: voce nao pode interpolar strings de country.

Varios percentis em uma unica passagem

No PostgreSQL voce pode passar um array de fracoes e obter um array de resultados em uma unica chamada, o que e mais barato que tres consultas separadas porque a ordenacao acontece uma vez.

-- p50, p90, p99 in a single ordered pass
SELECT PERCENTILE_DISC(ARRAY[0.5, 0.9, 0.99])
         WITHIN GROUP (ORDER BY amount) AS p
FROM orders;
-- p -> {200, 980, 1500}

E pratico desdobrar o array em colunas de imediato:

SELECT p[1] AS p50, p[2] AS p90, p[3] AS p99
FROM (
  SELECT PERCENTILE_DISC(ARRAY[0.5, 0.9, 0.99])
           WITHIN GROUP (ORDER BY amount) AS p
  FROM orders
) t;

Combina muito bem com GROUP BY quando voce precisa de percentis por segmento:

SELECT dept,
       PERCENTILE_DISC(ARRAY[0.25, 0.5, 0.75])
         WITHIN GROUP (ORDER BY salary) AS quartiles
FROM employees
GROUP BY dept;

Pegadinhas e diferencas entre motores

  • NULL no ORDER BY e ignorado ao calcular a posicao, assim como em outros agregados. Geralmente e o que voce quer, mas verifique se os NULL sao significativos.
  • Voce nao pode substituir WITHIN GROUP por um ORDER BY comum dentro dos parenteses; isso e um erro de sintaxe.
  • O MySQL nao tem PERCENTILE_DISC de forma alguma; percentis sao emulados com funcoes de janela e NTILE/ROW_NUMBER.
  • No ClickHouse o equivalente e quantileExact(0.5)(amount): ele tambem retorna um valor real dos dados, enquanto quantile() interpola.
-- ClickHouse: exact (discrete) vs interpolated
SELECT
  quantileExact(0.5)(amount) AS disc_median,
  quantile(0.5)(amount)      AS cont_median
FROM orders;

A regra decisiva: se o resultado precisa ser lido como "uma linha real da tabela", use PERCENTILE_DISC; se voce quer uma estimativa estatistica suave do centro ou de uma cauda, use PERCENTILE_CONT.

Pratique com exercícios reais

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

Abrir o treinador