2.7. Funções de agregação #

Como a maioria dos produtos de banco de dados relacional, o PostgreSQL aceita funções de agregação. Uma função de agregação calcula um único resultado para várias linhas de entrada. Por exemplo, existem funções de agregação para contar (count), somar (sum), calcular a média (avg), o valor máximo (max) e o valor mínimo (min) para um conjunto de linhas.

Para servir de exemplo, é possível encontrar a maior temperatura mínima observada em qualquer lugar usando:

SELECT max(temp_min)
    FROM clima;

 max
-----
  23
(1 linha)

Se for desejado saber a cidade (ou cidades) onde essa temperatura ocorreu, pode-se tentar usar

SELECT cidade
    FROM clima
    WHERE temp_min = max(temp_min);  -- ERRADO

mas não vai funcionar, porque a função de agregação max não pode ser usada na cláusula WHERE (Esta restrição existe, porque a cláusula WHERE determina quais linhas serão incluídas no cálculo da agregação e, nesse caso, teria que ser avaliada antes das funções de agregação serem computadas). Entretanto, como é geralmente o caso, a consulta pode ser reformulada para obter o resultado pretendido, o que será feito aqui por meio de uma subconsulta:

SELECT cidade
    FROM clima
    WHERE temp_min = (SELECT max(temp_min) FROM clima);

    cidade
---------------
 Salvador - BA
(1 linha)

Isso está correto, porque a subconsulta é uma ação independente, que calcula sua agregação de forma isolada do que está acontecendo na consulta externa.

As agregações também são muito úteis em combinação com a cláusula GROUP BY. Por exemplo, pode ser obtida a maior temperatura mínima observada em cada cidade usando:

SELECT cidade, max(temp_min)
    FROM clima
    GROUP BY cidade;

       cidade        | max
---------------------+-----
 Rio de Janeiro - RJ |  17
 Salvador - BA       |  23
(2 linhas)

produzindo uma linha de saída para cada cidade. Cada resultado da agregação é computado sobre as linhas da tabela correspondendo a uma cidade. As linhas agrupadas podem ser filtradas utilizando a cláusula HAVING:

SELECT cidade, max(temp_min)
    FROM clima
    GROUP BY cidade
    HAVING max(temp_min) < 30;

       cidade        | max
---------------------+-----
 Rio de Janeiro - RJ |  17
 Salvador - BA       |  23
(2 linhas)

mostrando os mesmos resultados, mas apenas para as cidades que possuem todas as temperaturas mínimas abaixo de 30. Para concluir, se desejarmos somente as cidades com nome começando pela letra S podemos escrever:

SELECT cidade, max(temp_min)
    FROM clima
    WHERE cidade LIKE 'S%'  -- (1)
    GROUP BY cidade
    HAVING max(temp_min) < 30;

    cidade     | max
---------------+-----
 Salvador - BA |  23
(1 linha)

(1)

O operador LIKE faz correspondência com padrão, sendo explicado na Seção 9.7.

É importante compreender a interação entre a agregação e as cláusulas WHERE e HAVING do SQL. A diferença fundamental entre WHERE e HAVING é essa: WHERE seleciona linhas da entrada antes que os grupos e agregações sejam formados (assim, controla quais linhas vão para o cálculo da agregação), enquanto HAVING seleciona as linhas de grupo após os grupos e agregações serem formados. Assim, a cláusula WHERE não deve conter funções de agregação; não faz sentido tentar usar uma agregação para determinar quais linhas servirão de entrada para a agregação. Por outro lado, a cláusula HAVING sempre contém funções de agregação. (Estritamente falando, é permitido escrever uma cláusula HAVING que não usa agregações, mas raramente é útil. A mesma condição pode ser usada de forma mais eficiente na cláusula WHERE.)

No exemplo anterior, podemos aplicar a restrição do nome da cidade na cláusula WHERE, porque ela não precisa de agregação. Isso é mais eficiente do que adicionar a restrição à cláusula HAVING, porque evitamos fazer os cálculos de agrupamento e agregação para todas as linhas que falhariam na condição da cláusula WHERE.

Outra forma de selecionar as linhas que entram em um cálculo de agregação é usar a cláusula FILTER, que é uma opção para cada agregação:

SELECT cidade, count(*) FILTER (WHERE temp_min < 20), max(temp_min)
    FROM clima
    GROUP BY cidade;

       cidade        | count | max
---------------------+-------+-----
 Rio de Janeiro - RJ |     1 |  17
 Salvador - BA       |     0 |  23
(2 linhas)

A cláusula FILTER é muito parecida com a cláusula WHERE, com a diferença de que remove linhas apenas da entrada da função de agregação específica à qual está associada. Aqui, a agregação count conta apenas as linhas com temp_min abaixo de 20; mas a agregação max ainda é aplicada a todas as linhas, então ainda encontra a leitura de 23.