GREATEST e LEAST recebem uma lista de argumentos e retornam o maior ou o menor entre eles dentro de uma mesma linha. Voce recorre a eles quando precisa comparar nao linhas entre si, mas varias colunas ou expressoes dentro da linha atual: escolher a mais recente entre duas datas, colocar um piso ou um teto em um numero, ou prender um valor dentro de um intervalo. Nao sao funcoes de agregacao: em vez de agrupar linhas, olham as colunas lado a lado, na horizontal, e emitem um valor por linha.
As duas funcoes compartilham a mesma assinatura: a entrada e uma lista de duas ou mais expressoes de tipo comparavel e a saida e um unico valor desse mesmo tipo. GREATEST retorna o maximo da lista e LEAST, o minimo. Voce pode misturar os argumentos a vontade (colunas, literais, resultados de outras chamadas), por isso as duas funcoes se aninham com tanta facilidade uma dentro da outra.
Convem verificar antes duas coisas: os tipos e o NULL. Os tipos precisam ser comparaveis entre si, ou o PostgreSQL os converte para um tipo comum ou se recusa a executar a consulta; e um NULL na lista de argumentos e tratado de forma diferente em cada banco, que e justamente onde uma consulta migrada costuma quebrar.
Como isso difere de MAX e MIN
A confusao nasce das palavras: um "maximo" e um "maximo". Mas as duas familias trabalham em direcoes diferentes. MAX e MIN sao agregados: percorrem verticalmente as linhas de um grupo e retornam um unico numero para todo o resultado. GREATEST e LEAST percorrem horizontalmente os argumentos de uma unica linha e retornam um valor por linha.
SELECT MAX(amount) AS biggest_order
FROM orders;
SELECT id, GREATEST(amount, 10) AS amount_floor_10
FROM orders;
Um caso classico e escolher a data mais recente entre varias diretamente na linha, sem subconsultas nem funcoes de janela:
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;
Limitar um valor a um intervalo (clamp)
O truque mais pratico com essas funcoes e empurrar um numero para um corredor entre um limite inferior e um superior. Isso se consegue aninhando uma chamada dentro da outra: GREATEST(lo, LEAST(hi, x)) garante que o resultado nunca escape de [lo, hi]. A formula se le de dentro para fora: primeiro corta por cima, depois eleva por baixo.
SELECT
id,
amount,
GREATEST(1, LEAST(1000, amount)) AS amount_clamped
FROM orders;
E indispensavel onde um valor precisa se manter dentro de limites: descontos, pontos de fidelidade, normalizacao de salarios para relatorios.
SELECT
id,
name,
GREATEST(30000, LEAST(200000, salary)) AS salary_banded
FROM employees;
Vejamos quem faz o que:
- o
GREATEST(lo, ...) externo eleva ate lo os valores pequenos demais;
- o
LEAST(hi, x) interno reduz ate hi os valores grandes demais;
- a ordem dos limites e critica: se voce definir
lo > hi por acidente, o corredor colapsa em uma contradicao e voce obtem um resultado sem sentido, em silencio e sem erro.
Tratamento de NULL: a principal armadilha
Aqui comeca a parte realmente traicoeira. O comportamento com NULL diverge entre os bancos, e e justamente o que quebra o codigo ao mudar de um banco para outro.
- PostgreSQL: argumentos
NULL sao simplesmente descartados da comparacao. GREATEST(5, NULL, 9) retorna 9. So quando todos os argumentos, sem excecao, sao NULL o resultado tambem e NULL.
- MySQL: basta um unico
NULL entre os argumentos para anular todo o resultado. GREATEST(5, NULL, 9) retorna NULL no MySQL: um NULL envenena a expressao inteira.
- ClickHouse: semanticamente esta mais perto do MySQL. Se ao menos um argumento do tipo
Nullable for NULL, o resultado tambem e NULL. Aqui voce nao pode contar com o descarte de NULL no estilo do PostgreSQL, entao nao guarde o ClickHouse como uma copia do PostgreSQL: proteja os argumentos de forma explicita.
SELECT GREATEST(5, NULL, 9) AS demo;
Pegadinha: nao conte com o "o NULL se descarta sozinho" ao migrar codigo do PostgreSQL para o MySQL ou o ClickHouse. Uma logica que funcionou em silencio por anos pode comecar de repente a retornar NULL apos a migracao, e detectar isso a olho nu em uma consulta grande e quase impossivel. Se uma coluna pode ser NULL, envolva-a em COALESCE para que o comportamento seja explicito e identico em todos os lugares.
SELECT
id,
GREATEST(COALESCE(amount, 0), 0) AS amount_nonneg
FROM orders;
Combinacoes uteis
GREATEST e LEAST se encaixam alem do SELECT. Dentro de um UPDATE e em expressoes calculadas, eles poupam voce de blocos CASE volumosos.
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;
Em resumo: GREATEST e LEAST comparam valores na horizontal, dentro de uma linha; limitam um numero a um intervalo de forma elegante por meio de uma chamada aninhada; e exigem protecao explicita com COALESCE se o mesmo codigo precisa se comportar igual no PostgreSQL, no MySQL e no ClickHouse. Aprenda a diferenca de NULL uma vez e voce economizara uma noite de depuracao mais tarde.
GREATESTeLEASTrecebem uma lista de argumentos e retornam o maior ou o menor entre eles dentro de uma mesma linha. Voce recorre a eles quando precisa comparar nao linhas entre si, mas varias colunas ou expressoes dentro da linha atual: escolher a mais recente entre duas datas, colocar um piso ou um teto em um numero, ou prender um valor dentro de um intervalo. Nao sao funcoes de agregacao: em vez de agrupar linhas, olham as colunas lado a lado, na horizontal, e emitem um valor por linha.As duas funcoes compartilham a mesma assinatura: a entrada e uma lista de duas ou mais expressoes de tipo comparavel e a saida e um unico valor desse mesmo tipo.
GREATESTretorna o maximo da lista eLEAST, o minimo. Voce pode misturar os argumentos a vontade (colunas, literais, resultados de outras chamadas), por isso as duas funcoes se aninham com tanta facilidade uma dentro da outra.Convem verificar antes duas coisas: os tipos e o
NULL. Os tipos precisam ser comparaveis entre si, ou o PostgreSQL os converte para um tipo comum ou se recusa a executar a consulta; e umNULLna lista de argumentos e tratado de forma diferente em cada banco, que e justamente onde uma consulta migrada costuma quebrar.Como isso difere de MAX e MIN
A confusao nasce das palavras: um "maximo" e um "maximo". Mas as duas familias trabalham em direcoes diferentes.
MAXeMINsao agregados: percorrem verticalmente as linhas de um grupo e retornam um unico numero para todo o resultado.GREATESTeLEASTpercorrem horizontalmente os argumentos de uma unica linha e retornam um valor por linha.-- 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;Um caso classico e escolher a data mais recente entre varias diretamente na linha, sem subconsultas nem funcoes de janela:
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;Limitar um valor a um intervalo (clamp)
O truque mais pratico com essas funcoes e empurrar um numero para um corredor entre um limite inferior e um superior. Isso se consegue aninhando uma chamada dentro da outra:
GREATEST(lo, LEAST(hi, x))garante que o resultado nunca escape de[lo, hi]. A formula se le de dentro para fora: primeiro corta por cima, depois eleva por baixo.-- Clamp the order amount into the range [1, 1000] SELECT id, amount, GREATEST(1, LEAST(1000, amount)) AS amount_clamped FROM orders;E indispensavel onde um valor precisa se manter dentro de limites: descontos, pontos de fidelidade, normalizacao de salarios para relatorios.
-- Cap every salary at 200000 but never below 30000 SELECT id, name, GREATEST(30000, LEAST(200000, salary)) AS salary_banded FROM employees;Vejamos quem faz o que:
GREATEST(lo, ...)externo eleva ateloos valores pequenos demais;LEAST(hi, x)interno reduz atehios valores grandes demais;lo > hipor acidente, o corredor colapsa em uma contradicao e voce obtem um resultado sem sentido, em silencio e sem erro.Tratamento de NULL: a principal armadilha
Aqui comeca a parte realmente traicoeira. O comportamento com
NULLdiverge entre os bancos, e e justamente o que quebra o codigo ao mudar de um banco para outro.NULLsao simplesmente descartados da comparacao.GREATEST(5, NULL, 9)retorna9. So quando todos os argumentos, sem excecao, saoNULLo resultado tambem eNULL.NULLentre os argumentos para anular todo o resultado.GREATEST(5, NULL, 9)retornaNULLno MySQL: umNULLenvenena a expressao inteira.NullableforNULL, o resultado tambem eNULL. Aqui voce nao pode contar com o descarte deNULLno estilo do PostgreSQL, entao nao guarde o ClickHouse como uma copia do PostgreSQL: proteja os 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;Combinacoes uteis
GREATESTeLEASTse encaixam alem doSELECT. Dentro de umUPDATEe em expressoes calculadas, eles poupam voce de blocosCASEvolumosos.-- 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;Em resumo:
GREATESTeLEASTcomparam valores na horizontal, dentro de uma linha; limitam um numero a um intervalo de forma elegante por meio de uma chamada aninhada; e exigem protecao explicita comCOALESCEse o mesmo codigo precisa se comportar igual no PostgreSQL, no MySQL e no ClickHouse. Aprenda a diferenca deNULLuma vez e voce economizara uma noite de depuracao mais tarde.