O que uma função de agregação realmente faz
A maioria das funções SQL que você viu até agora trabalha linha a linha: UPPER(name) roda uma vez por linha, ROUND(price, 2) roda uma vez por linha. As funções de agregação no SQLite funcionam de outro jeito. Elas olham para um conjunto inteiro de linhas e condensam tudo em um único valor.
Crie uma tabelinha para brincar:
Cinco linhas entram, uma linha sai. Esse é o modelo mental por trás das funções de agregação no SQLite: elas comprimem várias linhas em um resumo. Sem um GROUP BY, esse resumo cobre todas as linhas do resultado.
SQL COUNT: linhas versus valores
O COUNT tem três variações, e a diferença entre elas faz toda a diferença:
COUNT(*)conta linhas. NULLs entram na contagem. Sempre retorna um número.COUNT(coluna)conta os valores não-NULL daquela coluna.COUNT(DISTINCT coluna)conta os valores únicos não-NULL.
São cinco linhas, três delas com amount preenchido e três clientes distintos. Se um dia você se deparar com COUNT(amount) e estranhar por que ele é menor que COUNT(*), é justamente por isso — NULLs ficam de fora.
SUM, AVG, MIN e MAX
As funções de agregação aritméticas se comportam como você imagina, com uma regrinha discreta: todas ignoram NULL:
AVG é (10 + 20 + 30) / 3 = 20.0, e não 60 / 4 = 15.0. O denominador conta apenas os valores não nulos. Se esse não for o comportamento que você quer — ou seja, se preferir tratar dados ausentes como zero — deixe isso explícito:
MIN e MAX também funcionam com texto e datas: no caso do texto, a comparação é lexicográfica; nas datas, basta usar o formato ISO padrão, que elas são comparadas como strings.
Diferença entre SUM e TOTAL no SQLite
O SQLite tem um segundo agregador parecido com soma, o TOTAL, que resolve dois incômodos do SUM:
SUMem zero linhas retornaNULL. JáTOTALretorna0.0.SUMquando todos os valores sãoNULLtambém retornaNULL.TOTALretorna0.0.TOTALsempre devolve um número de ponto flutuante, então nunca dá overflow de inteiro.
O preço dessa praticidade: TOTAL não faz parte do SQL padrão, e o resultado sempre REAL pode te pegar de surpresa se você esperava um inteiro. Use TOTAL quando "nenhuma linha significa zero" fizer sentido na sua aplicação, e fique com SUM quando quiser o comportamento padrão do SQL.
DISTINCT dentro de funções de agregação
O DISTINCT pode ser usado dentro de qualquer função de agregação, não só no COUNT. Ele remove os valores duplicados antes da agregação rodar:
SUM(amount) soma o valor de todas as linhas. Já SUM(DISTINCT amount) soma cada valor único uma única vez — útil em casos como "total de valores únicos de notas fiscais", mas raramente é o que você quer. O mais usado no dia a dia é o COUNT(DISTINCT customer).
FILTER: agregando apenas um subconjunto
Quando você quer agregar só algumas linhas, o caminho óbvio é o WHERE. O problema é que o WHERE filtra tudo — não dá pra "contar pedidos pagos" e "contar reembolsos" na mesma consulta desse jeito. É aí que entra o FILTER:
Cada cláusula FILTER (WHERE ...) se aplica apenas àquele agregado específico. Você faz uma única varredura na tabela e obtém vários recortes resumidos de uma vez. Antes do FILTER existir, a galera escrevia SUM(CASE WHEN status = 'paid' THEN amount END) — mesma ideia, só que com bem mais código.
GROUP_CONCAT no SQLite: juntando strings
O GROUP_CONCAT é o patinho feio do grupo. Em vez de devolver um número, ele concatena os valores em uma única string:
O separador padrão é a vírgula. Se quiser outro caractere, basta passar um segundo argumento. A ordem não é garantida, a menos que você use GROUP_CONCAT(tag ORDER BY tag) — isso é útil quando o resultado vai aparecer em alguma tela e você quer manter a saída consistente.
Agregação sem GROUP BY
Todos os exemplos anteriores que usaram funções de agregação sem GROUP BY retornaram exatamente uma linha. E essa é a regra: um SELECT com agregações e sem GROUP BY gera um resumo de linha única da tabela inteira (depois de aplicado o WHERE).
Você pode combinar várias agregações à vontade:
O que você não pode fazer é misturar colunas não agregadas com funções de agregação e esperar resultados que façam sentido:
-- Permitido pelo SQLite, mas o valor de `customer` é arbitrário.
SELECT customer, SUM(amount) FROM orders;
O SQLite não vai reclamar disso (outros bancos vão), mas ele vai escolher o nome de algum cliente aleatório para mostrar junto com o total. Se você quer a soma por cliente, precisa do GROUP BY — que é o assunto da próxima página.
A seguir: GROUP BY e HAVING
Agregações sobre a tabela inteira respondem "quanto deu no total". Já as agregações por grupo — por cliente, por mês, por status — respondem perguntas bem mais interessantes. O GROUP BY é a forma de separar as linhas em baldes antes de agregar, e o HAVING é como você filtra em cima do resultado agregado. É isso que vem na sequência.
Perguntas frequentes
O que são funções de agregação no SQLite?
Funções de agregação pegam várias linhas e devolvem um único valor de resumo. As nativas são COUNT, SUM, AVG, MIN, MAX, TOTAL e GROUP_CONCAT. Sem um GROUP BY, elas reduzem todo o resultado da consulta a uma só linha.
Qual a diferença entre SUM e TOTAL no SQLite?
Os dois somam números, mas o SUM retorna NULL quando todas as entradas são NULL e usa aritmética inteira sempre que possível (o que pode causar overflow). Já o TOTAL sempre devolve um número de ponto flutuante e retorna 0.0 quando não há linhas. Use TOTAL quando precisar garantir um resultado numérico, e SUM quando o comportamento padrão do SQL for importante.
Como contar valores distintos no SQLite?
Coloque o DISTINCT dentro da chamada: COUNT(DISTINCT customer_id). Isso conta os valores únicos não nulos. O COUNT(coluna) simples conta valores não nulos incluindo duplicatas, e o COUNT(*) conta todas as linhas, independentemente de NULLs.
As funções de agregação do SQLite ignoram NULL?
Sim — todas as agregações, com exceção do COUNT(*), descartam entradas NULL. O AVG, por exemplo, divide pela quantidade de valores não nulos, e não pelo total de linhas. A exceção é o COUNT(*), que conta linhas e não valores, então os NULLs entram na conta.