sqlpostgresqlntilewindow-functions

NTILE en SQL: cuartiles, deciles y cohortes en cubos iguales

Como NTILE(n) divide filas ordenadas en n cubos casi iguales, donde cae el resto y en que se diferencia de WIDTH_BUCKET.

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

NTILE(n) es una funcion de ventana que divide un conjunto ordenado de filas en n cubos lo mas iguales posible en tamano y etiqueta cada fila con un numero de cubo de 1 a n. Es el caballo de batalla de cuartiles, deciles y cualquier cohorte de "igual numero de clientes por grupo".

El caso basico: cuartiles

Dividimos a los usuarios en cuatro grupos iguales por gasto total. ORDER BY ... DESC significa que el cubo 1 es el "top".

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;

La diferencia clave frente a RANK/ROW_NUMBER: NTILE mira el numero de filas, no los valores concretos. Diez filas con NTILE(4) producen cubos de tamano 3, 3, 2, 2 — la funcion coloca las filas por ti.

Como se reparte el resto

Cuando el numero de filas no se divide exacto entre n, las filas sobrantes van a los cubos de numero mas bajo. Para 10 filas y NTILE(4): el resto es 10 mod 4 = 2, asi que los dos primeros cubos reciben una fila extra cada uno.

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

Trampa: cuando n es mayor que el numero de filas, algunos cubos nunca aparecen. Siete filas con NTILE(10) producen numeros del 1 al 7, y los cubos 8, 9, 10 quedan vacios. No supongas que todos los cubos existen.

Una segunda trampa: los valores iguales (empates) pueden caer en cubos distintos, porque se cuentan filas, no valores. Si el limite de una cohorte debe coincidir con un limite de valor, NTILE es la herramienta equivocada — usa WIDTH_BUCKET o un CASE manual.

PARTITION BY: deciles dentro de cada pais

PARTITION BY recalcula los cubos de forma independiente dentro de cada grupo. Abajo, deciles 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 obtiene su propia escalera del 1 al 10. La misma idea da deciles de salario por departamento en una sola consulta:

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

NTILE frente a WIDTH_BUCKET

Es facil confundirlos, pero la logica es distinta:

  • NTILE(n) iguala el numero de filas entre los cubos. Cada cubo tiene mas o menos el mismo tamano, pero los rangos de valores fluctuan.
  • WIDTH_BUCKET(val, lo, hi, n) iguala el ancho del rango. Los cubos abarcan anchos de valor iguales, pero su numero de filas puede variar muchisimo.
-- Bandas de igual ANCHO: 0-250, 250-500, 500-750, 750-1000
SELECT id, amount,
  WIDTH_BUCKET(amount, 0, 1000, 4) AS price_band
FROM orders;

Regla practica: "igual numero de clientes por grupo" → NTILE; "bandas de precio fijas" → WIDTH_BUCKET.

Compatibilidad

  • PostgreSQL — soporta por completo tanto NTILE como WIDTH_BUCKET.
  • MySQL — NTILE existe desde la 8.0; no hay WIDTH_BUCKET, asi que se emula con una expresion FLOOR(...).
  • ClickHouse — no hay NTILE nativo; usa intDiv(rowNumberInAllBlocks(), ...) o aritmetica sobre row_number().

Lo que hay que recordar: NTILE corta por filas, y el resto siempre se inclina hacia los cubos de numero mas bajo — eso es lo que hace que las cohortes por cuartiles sean honestas.

Practica con ejercicios reales

Resuelve ejercicios en el entrenador de SQL con corrección instantánea y pistas.

Abrir el entrenador