GREATEST y LEAST toman una lista de argumentos y devuelven el mayor o el menor de ellos dentro de una misma fila. Se recurre a ellos cuando no hay que comparar filas entre si, sino varias columnas o expresiones dentro de la fila actual: elegir la mas reciente de dos fechas, poner un suelo o un techo a un numero, o acotar un valor a un rango. No son funciones de agregacion: en lugar de agrupar filas, miran las columnas una junto a otra, en horizontal, y emiten un valor por cada fila.
Ambas funciones comparten la misma firma: la entrada es una lista de dos o mas expresiones de tipo comparable y la salida es un unico valor de ese mismo tipo. GREATEST devuelve el maximo de la lista y LEAST el minimo. Puedes mezclar los argumentos con libertad (columnas, literales, resultados de otras llamadas), por eso ambas funciones se anidan con tanta facilidad una dentro de otra.
Conviene revisar antes dos cosas: los tipos y el NULL. Los tipos deben ser comparables entre si, o PostgreSQL los convertira a un tipo comun o se negara a ejecutar la consulta; y un NULL en la lista de argumentos se trata de forma distinta en cada motor, que es justo donde suele romperse una consulta migrada.
En que se diferencian de MAX y MIN
La confusion nace de las palabras: un "maximo" es un "maximo". Pero las dos familias trabajan en direcciones distintas. MAX y MIN son agregados: recorren verticalmente las filas de un grupo y devuelven un solo numero para todo el resultado. GREATEST y LEAST recorren horizontalmente los argumentos de una sola fila y devuelven un valor por cada fila.
SELECT MAX(amount) AS biggest_order
FROM orders;
SELECT id, GREATEST(amount, 10) AS amount_floor_10
FROM orders;
Un caso clasico es elegir la fecha mas reciente entre varias dentro de la propia fila, sin subconsultas ni funciones de ventana:
SELECT
u.id,
GREATEST(u.created_at, o.created_at) AS last_touch
FROM users u
JOIN orders o ON o.user_id = u.id;
Acotar un valor a un rango (clamp)
El truco mas practico con estas funciones es meter un numero en un pasillo entre un limite inferior y uno superior. Se logra anidando una llamada dentro de otra: GREATEST(lo, LEAST(hi, x)) garantiza que el resultado nunca se salga de [lo, hi]. La formula se lee de dentro hacia fuera: primero recortas por arriba y luego elevas por abajo.
SELECT
id,
amount,
GREATEST(1, LEAST(1000, amount)) AS amount_clamped
FROM orders;
Es imprescindible alli donde un valor debe mantenerse dentro de unos limites: descuentos, puntos de fidelidad, normalizacion de salarios para informes.
SELECT
id,
name,
GREATEST(30000, LEAST(200000, salary)) AS salary_banded
FROM employees;
Veamos quien hace que:
- el
GREATEST(lo, ...) exterior eleva hasta lo los valores demasiado pequenos;
- el
LEAST(hi, x) interior baja hasta hi los valores demasiado grandes;
- el orden de los limites es critico: si por accidente pones
lo > hi, el pasillo se colapsa en una contradiccion y obtienes un resultado sin sentido, en silencio y sin error.
Manejo de NULL: la mina principal
Aqui empieza lo realmente traicionero. El comportamiento con NULL diverge entre motores, y es justo lo que rompe el codigo al pasar de un motor a otro.
- PostgreSQL: los argumentos
NULL simplemente se descartan de la comparacion. GREATEST(5, NULL, 9) devuelve 9. Solo cuando todos y cada uno de los argumentos son NULL el resultado tambien es NULL.
- MySQL: basta un solo
NULL entre los argumentos para anular todo el resultado. GREATEST(5, NULL, 9) devuelve NULL en MySQL: un NULL envenena la expresion entera.
- ClickHouse: semanticamente esta mas cerca de MySQL. Si aunque sea un argumento de tipo
Nullable vale NULL, el resultado tambien es NULL. Aqui no puedes confiar en el salto de NULL al estilo de PostgreSQL, asi que no archives ClickHouse como una copia de PostgreSQL: protege los argumentos de forma explicita.
SELECT GREATEST(5, NULL, 9) AS demo;
Trampa: no cuentes con que "el NULL se descarta solo" al migrar codigo de PostgreSQL a MySQL o ClickHouse. Una logica que funciono en silencio durante anos puede empezar de repente a devolver NULL tras la migracion, y detectarlo a simple vista en una consulta grande es casi imposible. Si una columna puede ser NULL, envuelvela en COALESCE para que el comportamiento sea explicito e identico en todas partes.
SELECT
id,
GREATEST(COALESCE(amount, 0), 0) AS amount_nonneg
FROM orders;
Combinaciones utiles
GREATEST y LEAST encajan mas alla del SELECT. Dentro de un UPDATE y en expresiones calculadas te ahorran bloques CASE voluminosos.
UPDATE employees
SET salary = GREATEST(40000, salary * 1.10)
WHERE dept = 'sales';
SELECT
u.id,
CURRENT_DATE - GREATEST(u.created_at, o.created_at)::date AS days_idle
FROM users u
JOIN orders o ON o.user_id = u.id;
En resumen: GREATEST y LEAST comparan valores en horizontal, dentro de una fila; acotan un numero a un rango con elegancia mediante una llamada anidada; y exigen una proteccion explicita con COALESCE si el mismo codigo debe comportarse igual en PostgreSQL, MySQL y ClickHouse. Aprende la diferencia de NULL una vez y te ahorraras una tarde de depuracion mas adelante.
GREATESTyLEASTtoman una lista de argumentos y devuelven el mayor o el menor de ellos dentro de una misma fila. Se recurre a ellos cuando no hay que comparar filas entre si, sino varias columnas o expresiones dentro de la fila actual: elegir la mas reciente de dos fechas, poner un suelo o un techo a un numero, o acotar un valor a un rango. No son funciones de agregacion: en lugar de agrupar filas, miran las columnas una junto a otra, en horizontal, y emiten un valor por cada fila.Ambas funciones comparten la misma firma: la entrada es una lista de dos o mas expresiones de tipo comparable y la salida es un unico valor de ese mismo tipo.
GREATESTdevuelve el maximo de la lista yLEASTel minimo. Puedes mezclar los argumentos con libertad (columnas, literales, resultados de otras llamadas), por eso ambas funciones se anidan con tanta facilidad una dentro de otra.Conviene revisar antes dos cosas: los tipos y el
NULL. Los tipos deben ser comparables entre si, o PostgreSQL los convertira a un tipo comun o se negara a ejecutar la consulta; y unNULLen la lista de argumentos se trata de forma distinta en cada motor, que es justo donde suele romperse una consulta migrada.En que se diferencian de MAX y MIN
La confusion nace de las palabras: un "maximo" es un "maximo". Pero las dos familias trabajan en direcciones distintas.
MAXyMINson agregados: recorren verticalmente las filas de un grupo y devuelven un solo numero para todo el resultado.GREATESTyLEASTrecorren horizontalmente los argumentos de una sola fila y devuelven un valor por cada fila.-- Aggregate: one number per group, scans many rows SELECT MAX(amount) AS biggest_order FROM orders; -- Row-wise: one value per row, compares columns side by side SELECT id, GREATEST(amount, 10) AS amount_floor_10 FROM orders;Un caso clasico es elegir la fecha mas reciente entre varias dentro de la propia fila, sin subconsultas ni funciones de ventana:
SELECT u.id, GREATEST(u.created_at, o.created_at) AS last_touch FROM users u JOIN orders o ON o.user_id = u.id;Acotar un valor a un rango (clamp)
El truco mas practico con estas funciones es meter un numero en un pasillo entre un limite inferior y uno superior. Se logra anidando una llamada dentro de otra:
GREATEST(lo, LEAST(hi, x))garantiza que el resultado nunca se salga de[lo, hi]. La formula se lee de dentro hacia fuera: primero recortas por arriba y luego elevas por abajo.-- Clamp the order amount into the range [1, 1000] SELECT id, amount, GREATEST(1, LEAST(1000, amount)) AS amount_clamped FROM orders;Es imprescindible alli donde un valor debe mantenerse dentro de unos limites: descuentos, puntos de fidelidad, normalizacion de salarios para informes.
-- Cap every salary at 200000 but never below 30000 SELECT id, name, GREATEST(30000, LEAST(200000, salary)) AS salary_banded FROM employees;Veamos quien hace que:
GREATEST(lo, ...)exterior eleva hastalolos valores demasiado pequenos;LEAST(hi, x)interior baja hastahilos valores demasiado grandes;lo > hi, el pasillo se colapsa en una contradiccion y obtienes un resultado sin sentido, en silencio y sin error.Manejo de NULL: la mina principal
Aqui empieza lo realmente traicionero. El comportamiento con
NULLdiverge entre motores, y es justo lo que rompe el codigo al pasar de un motor a otro.NULLsimplemente se descartan de la comparacion.GREATEST(5, NULL, 9)devuelve9. Solo cuando todos y cada uno de los argumentos sonNULLel resultado tambien esNULL.NULLentre los argumentos para anular todo el resultado.GREATEST(5, NULL, 9)devuelveNULLen MySQL: unNULLenvenena la expresion entera.NullablevaleNULL, el resultado tambien esNULL. Aqui no puedes confiar en el salto deNULLal estilo de PostgreSQL, asi que no archives ClickHouse como una copia de PostgreSQL: protege los argumentos de forma explicita.-- PostgreSQL: returns 9, NULL is ignored -- MySQL and ClickHouse: return NULL, one NULL poisons the result SELECT GREATEST(5, NULL, 9) AS demo;-- Portable: define a neutral fallback before comparing SELECT id, GREATEST(COALESCE(amount, 0), 0) AS amount_nonneg FROM orders;Combinaciones utiles
GREATESTyLEASTencajan mas alla delSELECT. Dentro de unUPDATEy en expresiones calculadas te ahorran bloquesCASEvoluminosos.-- Bump salary by 10% but never below the floor of 40000 UPDATE employees SET salary = GREATEST(40000, salary * 1.10) WHERE dept = 'sales';-- Days since the more recent of signup or first order SELECT u.id, CURRENT_DATE - GREATEST(u.created_at, o.created_at)::date AS days_idle FROM users u JOIN orders o ON o.user_id = u.id;En resumen:
GREATESTyLEASTcomparan valores en horizontal, dentro de una fila; acotan un numero a un rango con elegancia mediante una llamada anidada; y exigen una proteccion explicita conCOALESCEsi el mismo codigo debe comportarse igual en PostgreSQL, MySQL y ClickHouse. Aprende la diferencia deNULLuna vez y te ahorraras una tarde de depuracion mas adelante.