PERCENTILE_DISC devuelve un percentil como uno de los valores que realmente existen en tus datos, sin interpolar entre vecinos. Eso lo convierte en la opcion correcta cuando "el promedio de dos filas" no tiene sentido: para categorias, precios discretos y cualquier caso en que el resultado deba ser un valor real de la tabla.
Sintaxis basica
PERCENTILE_DISC es una agregacion de conjunto ordenado (ordered-set aggregate), por eso usa la sintaxis especial WITHIN GROUP (ORDER BY ...). El argumento entre parentesis es una fraccion entre 0 y 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 se elige el valor: las filas se ordenan por la clausula ORDER BY y luego se devuelve el primer valor cuya fraccion acumulada de filas alcanza el objetivo. El resultado siempre es igual a uno de los valores de la columna.
Discreto frente a continuo
PERCENTILE_CONT interpola: con un numero par de filas la mediana es el promedio de las dos centrales. PERCENTILE_DISC no interpola; entrega un valor real. Compara ambos en una sola 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;
Diferencias clave:
DISC siempre devuelve un valor presente en los datos; CONT puede devolver un numero que no esta en la tabla.
CONT requiere un tipo numerico (o intervalo). DISC tambien funciona con texto, fechas y enums, cualquier tipo ordenable.
- En muestras grandes sus resultados convergen, pero en pequenas divergen de forma notable.
Cuando elegir el discreto
Usa PERCENTILE_DISC cuando un valor intermedio carece de sentido o es invalido:
- Datos categoricos y de texto: un estado o pais "mediano" debe ser una cadena real, no una invencion.
- Importes y precios discretos donde no puede existir "250.5".
- SLA e informes donde debes mostrar un valor concreto y 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 simplemente no se ejecutara: no puedes interpolar cadenas de country.
Varios percentiles en una sola pasada
En PostgreSQL puedes pasar un array de fracciones y obtener un array de resultados con una sola llamada, lo que es mas barato que tres consultas separadas porque el ordenamiento ocurre una vez.
SELECT PERCENTILE_DISC(ARRAY[0.5, 0.9, 0.99])
WITHIN GROUP (ORDER BY amount) AS p
FROM orders;
Es comodo desplegar el array en columnas de inmediato:
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 muy bien con GROUP BY cuando necesitas percentiles 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;
Trampas y diferencias entre motores
NULL en el ORDER BY se ignora al calcular la posicion, igual que en otros agregados. Suele ser lo que quieres, pero verificalo si los NULL son significativos.
- No puedes reemplazar
WITHIN GROUP por un ORDER BY normal dentro de los parentesis; eso es un error de sintaxis.
- MySQL no tiene
PERCENTILE_DISC en absoluto; los percentiles se emulan con funciones de ventana y NTILE/ROW_NUMBER.
- En ClickHouse el equivalente es
quantileExact(0.5)(amount): tambien devuelve un valor real de los datos, mientras que quantile() interpola.
SELECT
quantileExact(0.5)(amount) AS disc_median,
quantile(0.5)(amount) AS cont_median
FROM orders;
La regla decisiva: si el resultado debe leerse como "una fila real de la tabla", usa PERCENTILE_DISC; si quieres una estimacion estadistica suave del centro o de una cola, usa PERCENTILE_CONT.
PERCENTILE_DISCdevuelve un percentil como uno de los valores que realmente existen en tus datos, sin interpolar entre vecinos. Eso lo convierte en la opcion correcta cuando "el promedio de dos filas" no tiene sentido: para categorias, precios discretos y cualquier caso en que el resultado deba ser un valor real de la tabla.Sintaxis basica
PERCENTILE_DISCes una agregacion de conjunto ordenado (ordered-set aggregate), por eso usa la sintaxis especialWITHIN GROUP (ORDER BY ...). El argumento entre parentesis es una fraccion entre 0 y 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 se elige el valor: las filas se ordenan por la clausula
ORDER BYy luego se devuelve el primer valor cuya fraccion acumulada de filas alcanza el objetivo. El resultado siempre es igual a uno de los valores de la columna.Discreto frente a continuo
PERCENTILE_CONTinterpola: con un numero par de filas la mediana es el promedio de las dos centrales.PERCENTILE_DISCno interpola; entrega un valor real. Compara ambos en una sola 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;Diferencias clave:
DISCsiempre devuelve un valor presente en los datos;CONTpuede devolver un numero que no esta en la tabla.CONTrequiere un tipo numerico (o intervalo).DISCtambien funciona con texto, fechas y enums, cualquier tipo ordenable.Cuando elegir el discreto
Usa
PERCENTILE_DISCcuando un valor intermedio carece de sentido o es 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_CONTsimplemente no se ejecutara: no puedes interpolar cadenas decountry.Varios percentiles en una sola pasada
En PostgreSQL puedes pasar un array de fracciones y obtener un array de resultados con una sola llamada, lo que es mas barato que tres consultas separadas porque el ordenamiento ocurre una 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}Es comodo desplegar el array en columnas de inmediato:
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 muy bien con
GROUP BYcuando necesitas percentiles 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;Trampas y diferencias entre motores
NULLen elORDER BYse ignora al calcular la posicion, igual que en otros agregados. Suele ser lo que quieres, pero verificalo si losNULLson significativos.WITHIN GROUPpor unORDER BYnormal dentro de los parentesis; eso es un error de sintaxis.PERCENTILE_DISCen absoluto; los percentiles se emulan con funciones de ventana yNTILE/ROW_NUMBER.quantileExact(0.5)(amount): tambien devuelve un valor real de los datos, mientras quequantile()interpola.-- ClickHouse: exact (discrete) vs interpolated SELECT quantileExact(0.5)(amount) AS disc_median, quantile(0.5)(amount) AS cont_median FROM orders;La regla decisiva: si el resultado debe leerse como "una fila real de la tabla", usa
PERCENTILE_DISC; si quieres una estimacion estadistica suave del centro o de una cola, usaPERCENTILE_CONT.