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.
SELECT PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY amount) AS median_amount
FROM orders;
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:
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.
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.
SELECT PERCENTILE_DISC(ARRAY[0.5, 0.9, 0.99])
WITHIN GROUP (ORDER BY amount) AS p
FROM orders;
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.
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.
PERCENTILE_DISCretorna 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_DISCe uma agregacao de conjunto ordenado (ordered-set aggregate), por isso usa a sintaxe especialWITHIN 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 BYe 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_CONTinterpola: com um numero par de linhas a mediana e a media das duas centrais.PERCENTILE_DISCnao 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:
DISCsempre retorna um valor presente nos dados;CONTpode retornar um numero que nao esta na tabela.CONTexige um tipo numerico (ou intervalo).DISCtambem funciona com texto, datas e enums, qualquer tipo ordenavel.Quando escolher o discreto
Use
PERCENTILE_DISCquando um valor intermediario nao faz sentido ou e invalido:-- 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_CONTsimplesmente nao roda: voce nao pode interpolar strings decountry.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 BYquando 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
NULLnoORDER BYe ignorado ao calcular a posicao, assim como em outros agregados. Geralmente e o que voce quer, mas verifique se osNULLsao significativos.WITHIN GROUPpor umORDER BYcomum dentro dos parenteses; isso e um erro de sintaxe.PERCENTILE_DISCde forma alguma; percentis sao emulados com funcoes de janela eNTILE/ROW_NUMBER.quantileExact(0.5)(amount): ele tambem retorna um valor real dos dados, enquantoquantile()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, usePERCENTILE_CONT.