sqlpostgresqlaggregationanalytics

MODE() WITHIN GROUP no SQL: o valor mais frequente em uma expressao

Como achar o valor mais frequente com MODE() WITHIN GROUP, como empates sao resolvidos e por que e melhor que GROUP BY + COUNT + LIMIT 1.

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

MODE() WITHIN GROUP (ORDER BY col) retorna o valor mais frequente de uma coluna — a moda estatistica — em uma unica expressao limpa, sem subconsultas nem funcoes de janela. E um agregado de conjunto ordenado, e o ORDER BY decide qual valor vence quando as frequencias empatam.

Sintaxe basica

A moda e o valor que aparece com mais frequencia. No PostgreSQL ela e escrita como um agregado de conjunto ordenado: tanto WITHIN GROUP quanto ORDER BY sao obrigatorios.

-- 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;

Vale lembrar:

  • O ORDER BY aqui nao ordena o resultado; ele nomeia a coluna sobre a qual a moda e calculada e fornece a regra de desempate.
  • NULL e ignorado na contagem de frequencias, como em qualquer agregado comum.
  • Funciona com qualquer tipo ordenavel: texto, numeros, datas, enums.

Moda por grupo

Na maioria das vezes voce quer a moda por recorte, nao global: o status mais frequente de cada usuario, o departamento mais popular por gerente. Adicione 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;

A expressao se le como uma unica coluna no SELECT, entao outros agregados — COUNT(*), SUM(amount) e por ai vai — convivem ao lado dela sem joins extras.

Resolucao de empates

O detalhe central: se varios valores compartilham a frequencia maxima, MODE() retorna aquele que vem primeiro segundo o ORDER BY informado. Isso torna o resultado deterministico — ao contrario de um LIMIT 1 sem ordenacao explicita.

-- 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;

Pegadinha: DESC no ORDER BY nao significa "o valor menos frequente". A moda continua sendo escolhida pela frequencia maxima; DESC so muda qual dos valores igualmente frequentes vence o empate. Achar um valor raro exige uma consulta totalmente diferente com COUNT(*) ordenado de forma ascendente.

Versus GROUP BY + COUNT + LIMIT 1

A forma classica de achar uma moda e contar frequencias e pegar a linha do topo:

-- 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, mas tem pontos fracos frente ao MODE():

  • Em um empate, LIMIT 1 pega uma linha arbitraria a menos que voce adicione uma segunda chave de ordenacao — o resultado nao e deterministico.
  • Para uma moda por grupo e preciso recorrer a uma funcao de janela (ROW_NUMBER() OVER (PARTITION BY ...)) ou a uma subconsulta correlacionada — bem mais codigo.
  • Nao da para colocar outros agregados do mesmo agrupamento ao lado em um SELECT simples.

O mesmo resultado "moda por usuario" com uma janela fica assim:

-- 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) com GROUP BY user_id faz a mesma coisa em uma linha.

Diferencas entre motores

MODE() como agregado de conjunto ordenado e SQL padrao, suportado por PostgreSQL, Oracle e DB2. Mas varios motores populares nao o tem:

  • MySQL e SQLite: nao ha funcao MODE(). Use GROUP BY ... ORDER BY COUNT(*) DESC LIMIT 1 ou a variante com janela acima.
  • ClickHouse: traz funcoes a parte — topK(1)(col) retorna um array com o valor mais frequente, e anyHeavy(col) da uma moda aproximada sobre fluxos grandes.
-- ClickHouse: most frequent value as a 1-element array
SELECT topK(1)(status) AS top_status FROM orders;

Resumindo: quando seu banco suporta MODE() WITHIN GROUP, e a forma mais curta, legivel e deterministica de obter o valor mais frequente — sobretudo com GROUP BY, onde a alternativa incha numa subconsulta com funcao de janela.

Pratique com exercícios reais

Resolva exercícios no treinador de SQL com correção instantânea e dicas.

Abrir o treinador