sqlpostgresqlaggregationanalytics

MODE() WITHIN GROUP en SQL: el valor mas frecuente en una sola expresion

Como encontrar el valor mas frecuente con MODE() WITHIN GROUP, como se resuelven los empates y por que es mejor que GROUP BY + COUNT + LIMIT 1.

3 min de lecturaReferencesql · postgresql · aggregation · analytics · statistics

MODE() WITHIN GROUP (ORDER BY col) devuelve el valor mas frecuente de una columna — la moda estadistica — en una sola expresion limpia, sin subconsultas ni funciones de ventana. Es un agregado de conjunto ordenado, y el ORDER BY decide que valor gana cuando las frecuencias empatan.

Sintaxis basica

La moda es el valor que aparece con mas frecuencia. En PostgreSQL se escribe como un agregado de conjunto ordenado: tanto WITHIN GROUP como ORDER BY son obligatorios.

-- Most frequent order status across the whole table
SELECT MODE() WITHIN GROUP (ORDER BY status) AS top_status
FROM orders;

-- Most common country among registered users
SELECT MODE() WITHIN GROUP (ORDER BY country) AS top_country
FROM users;

Conviene tener presente:

  • El ORDER BY aqui no ordena el resultado; nombra la columna sobre la que se calcula la moda y aporta la regla de desempate.
  • NULL se ignora al contar frecuencias, igual que en los agregados normales.
  • Funciona con cualquier tipo ordenable: texto, numeros, fechas, enums.

Moda por grupo

La mayoria de las veces quieres la moda por segmento, no global: el estado mas frecuente de cada usuario, el departamento mas popular por gerente. Agrega GROUP BY.

-- Most frequent status per user
SELECT user_id,
       MODE() WITHIN GROUP (ORDER BY status) AS usual_status
FROM orders
GROUP BY user_id;

-- Dominant department per manager
SELECT manager_id,
       MODE() WITHIN GROUP (ORDER BY dept) AS main_dept
FROM employees
GROUP BY manager_id;

La expresion se lee como una sola columna en el SELECT, asi que otros agregados — COUNT(*), SUM(amount), etc. — conviven a su lado sin joins extra.

Resolucion de empates

El detalle clave: si varios valores comparten la frecuencia maxima, MODE() devuelve el que va primero segun el ORDER BY indicado. Eso hace el resultado determinista — a diferencia de un LIMIT 1 sin orden explicito.

-- On a tie, the alphabetically smallest status wins
SELECT MODE() WITHIN GROUP (ORDER BY status) AS top_status
FROM orders;

-- Force the tie to resolve toward the largest amount instead
SELECT MODE() WITHIN GROUP (ORDER BY amount DESC) AS top_amount
FROM orders;

Trampa: DESC en el ORDER BY no significa "el valor menos frecuente". La moda se elige igualmente por frecuencia maxima; DESC solo cambia cual de los valores igual de frecuentes gana el empate. Encontrar un valor raro requiere una consulta totalmente distinta con COUNT(*) ordenado ascendente.

Frente a GROUP BY + COUNT + LIMIT 1

La forma clasica de hallar una moda es contar frecuencias y tomar la fila superior:

-- Old-school: count, sort, take the top row
SELECT status, COUNT(*) AS cnt
FROM orders
GROUP BY status
ORDER BY cnt DESC
LIMIT 1;

Funciona, pero tiene puntos debiles frente a MODE():

  • En un empate, LIMIT 1 toma una fila arbitraria salvo que agregues una segunda clave de orden — el resultado no es determinista.
  • Para una moda por grupo hay que recurrir a una funcion de ventana (ROW_NUMBER() OVER (PARTITION BY ...)) o a una subconsulta correlacionada — bastante mas codigo.
  • No puedes poner otros agregados de la misma agrupacion a su lado en un SELECT simple.

El mismo resultado "moda por usuario" con una ventana se ve asi:

-- Per-group mode the hard way
SELECT user_id, status AS usual_status
FROM (
  SELECT user_id, status, COUNT(*) AS cnt,
         ROW_NUMBER() OVER (PARTITION BY user_id
                            ORDER BY COUNT(*) DESC, status) AS rn
  FROM orders
  GROUP BY user_id, status
) t
WHERE rn = 1;

MODE() WITHIN GROUP (ORDER BY status) con GROUP BY user_id hace lo mismo en una linea.

Diferencias entre motores

MODE() como agregado de conjunto ordenado es SQL estandar, soportado por PostgreSQL, Oracle y DB2. Pero varios motores populares no lo tienen:

  • MySQL y SQLite: no hay funcion MODE(). Usa GROUP BY ... ORDER BY COUNT(*) DESC LIMIT 1 o la variante con ventana de arriba.
  • ClickHouse: trae funciones aparte — topK(1)(col) devuelve un arreglo con el valor mas frecuente, y anyHeavy(col) da una moda aproximada sobre flujos grandes.
-- ClickHouse: most frequent value as a 1-element array
SELECT topK(1)(status) AS top_status FROM orders;

En resumen: cuando tu base de datos soporta MODE() WITHIN GROUP, es la forma mas corta, legible y determinista de obtener el valor mas frecuente — sobre todo con GROUP BY, donde la alternativa se infla en una subconsulta con funcion de ventana.

Practica con ejercicios reales

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

Abrir el entrenador