O padrão ANSI SQL define (capítulo 6.5, especificação de função definida) o seguinte comportamento para funções agregadas em conjuntos de resultados vazios:
COUNT(...) = 0
AVG(...) = NULL
MIN(...) = NULL
MAX(...) = NULL
SUM(...) = NULL
Retornar NULL para AVG, MIN e MAX faz todo o sentido, já que a média, mínimo e máximo de um conjunto vazio é indefinido.
A última, porém, me incomoda: Matematicamente, a SOMA de um conjunto vazio está bem definida: 0
. Usando 0, o elemento neutro da adição, como o caso base torna tudo consistente:
SUM({}) = 0 = 0
SUM({5}) = 5 = 0 + 5
SUM({5, 3}) = 8 = 0 + 5 + 3
SUM({5, NULL}) = NULL = 0 + 5 + NULL
Definir SUM({})
como null
basicamente torna "sem linhas" um caso especial que não se encaixa nos outros:
SUM({}) = NULL = NULL
SUM({5}) = 5 != NULL + 5 (= NULL)
SUM({5, 3}) = 8 != NULL + 5 + 3 (= NULL)
Existe alguma vantagem óbvia da escolha que foi feita (SUM sendo NULL) que eu perdi?
Receio que o motivo seja simplesmente que as regras foram definidas de maneira ad hoc (como muitos outros "recursos" do padrão ISO SQL) em uma época em que as agregações SQL e sua conexão com a matemática eram menos compreendidas do que são agora (*).
É apenas uma das muitas inconsistências na linguagem SQL. Eles tornam o idioma mais difícil de ensinar, mais difícil de aprender, mais difícil de entender, mais difícil de usar, mais difícil de fazer o que você quiser, mas é assim que as coisas são. As regras não podem ser alteradas "frio" e "simplesmente", por motivos óbvios de compatibilidade com versões anteriores (se o comitê ISO publicar uma versão final do padrão e os fornecedores decidirem implementar esse padrão, esses fornecedores não apreciarão muito se em uma versão subseqüente, as regras forem alteradas de forma que as implementações existentes (compatíveis) da versão anterior do padrão "não cumpram automaticamente" a nova versão ...)
(*) Agora é melhor entender que as agregações sobre um conjunto vazio se comportam de maneira mais consistente se retornarem sistematicamente o valor de identidade (= o que você chama de 'elemento neutro') do operador binário subjacente em questão. Esse operador binário subjacente para COUNT e SUM é a adição e seu valor de identidade é zero. Para MIN e MAX, esse valor de identidade é o valor mais alto e mais baixo do tipo em questão, respectivamente, se os tipos em questão forem finitos. Casos como média, meios harmônicos, medianas, etc. são extremamente intrincados e exóticos a esse respeito.
Em um sentido pragmático, o resultado existente de
NULL
é útil. Considere a seguinte tabela e declarações:A primeira instrução retorna NULL e a segunda retorna zero. Se um conjunto vazio retornasse zero
SUM
, precisaríamos de outro meio para distinguir uma verdadeira soma de zero de um conjunto vazio, talvez usando contagem. Se realmente quisermos zero para o conjunto vazio, um simplesCOALESCE
fornecerá esse requisito.A principal diferença que posso ver é em relação ao tipo de dados. COUNT tem um tipo de retorno bem definido: Um número inteiro. Todos os outros dependem do tipo de coluna/expressão que estão olhando. Seu tipo de retorno deve ser compatível com todos os membros do conjunto (pense em float, moeda, decimal, bcd, timespan, ...). Como não há conjunto, você não pode sugerir um tipo de retorno, portanto, NULL é sua melhor opção.
Observação: na maioria dos casos, você pode sugerir um tipo de retorno do tipo de coluna que está vendo, mas pode fazer somas não apenas em colunas, mas em todos os tipos de coisas. Insinuar um tipo de retorno pode ser muito difícil, se não impossível, em certas circunstâncias, especialmente quando você pensa em possíveis expansões do padrão (tipos dinâmicos vêm à mente).