Por que o ANSI SQL define SUM (sem linhas) como NULL?

28

O padrão ANSI SQL define (capítulo 6.5, definir especificação de função) 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, pois a média, o mínimo e o máximo de um conjunto vazio são indefinidos.

O último, no entanto, me incomoda: Matematicamente, a soma de um conjunto vazio é bem definida: 0. Usando 0, o elemento neutro de 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 nullbasicamente 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?

Heinzi
fonte
Nota: Esta é uma versão generalizada de uma pergunta que fiz no StackOverflow especificamente sobre o SQL Server .
Heinzi 4/10/12
5
Sim, eu concordo: COUNT e SUM não se comportam de maneira consistente.
AK

Respostas:

20

Receio que o motivo seja simplesmente o fato de as regras terem sido definidas de forma ad-hoc (como muitas outras "características" do padrão ISO SQL) em um momento 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 extremamente 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 para o que você quiser, mas é assim que as coisas são. As regras não podem ser alteradas como "frias" e "exatamente assim", por razões óbvias de compatibilidade com versões anteriores (se o comitê da ISO publicar uma versão final do padrão e os fornecedores começarem a implementar esse padrão, esses fornecedores não apreciarão muito se, em uma versão subsequente, as regras forem alteradas para que as implementações existentes (compatíveis) da versão anterior do padrão "falhem automaticamente em cumprir" a nova versão ...)

(*) Agora é melhor entender que agregações em um conjunto vazio se comportam de maneira mais consistente se retornarem sistematicamente o valor da 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 é uma 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 são finitos. Casos como média, meios harmônicos, medianas etc. são extremamente intricados e exóticos a esse respeito.

Erwin Smout
fonte
Eu acho que nulo faz sentido sobre um conjunto vazio com min e max. Você pode dizer que um valor de identidade é realmente desconhecido, mas a soma de nenhum valor é 0 pelo mesmo motivo que n * 0 é sempre 0. Mas min e max são diferentes. Não acho que o resultado esteja definido corretamente, sem registros.
Chris Travers
Também avg () sobre um conjunto nulo faz sentido como um nulo porque 0/0 não está definido corretamente nesse contexto.
Chris Travers
5
MIN e MAX não são tão diferentes. Tome um operador binário subjacente LOWESTOF (x, y) e HIGHESTOF (x, y), respectivamente. Esses operadores binários têm um valor de identidade. Como em ambos os casos (se o tipo envolvido é finito), existe de fato algum valor z tal que todos x: LOWESTOF (z, x) = x e todos y: HIGHESTOF (y, z) = y. (O valor da identidade não é o mesmo nos dois casos, mas existe nos dois casos.) Concordo que os resultados parecem extremamente contra-intuitivos à primeira vista, mas não há como negar a realidade matemática.
Erwin Smout
@Erwin: Concordo em todos os seus pontos, a não ser que a identidade de algumas operações, como HIGHEST()muitos não ser um elemento do tipo de dados, como por reais, onde a identidade seria o -Infinity(e +Infinitypara LOWEST())
ypercubeᵀᴹ
1
@SQL kiwi. Você está esquecendo a verificação de tipo estático? Se expressões como SUM () são tratadas pelo verificador de tipo estático como se sempre retornassem um número inteiro, obviamente seria impossível para a chamada SUM () retornar algumas vezes algo que não é um número inteiro (por exemplo, uma relação vazia).
precisa saber é o seguinte
3

Num sentido pragmático, o resultado existente NULLé útil. Considere a seguinte tabela e instruções:

C1 C2
-- --
 1  3 
 2 -1 
 3 -2 

SELECT SUM(C2) FROM T1 WHERE C1 > 9;

SELECT SUM(C2) FROM T1 WHERE C1 < 9;

A primeira instrução retorna NULL e a segunda retorna zero. Se um conjunto vazio retornasse zero SUM, precisaríamos de outros meios para distinguir uma soma verdadeira de zero de um conjunto vazio, talvez usando count. Se realmente queremos zero para o conjunto vazio, um simples COALESCEfornecerá esse requisito.

SELECT COALESCE(SUM(C2),0) FROM T1 WHERE C1 > 9;
Leigh Riffel
fonte
1
como resultado., SUM (união do conjunto1 e do conjunto2) <> SUM (conjunto1) + SUM (conjunto2), porque qualquer número + NULL = NULL. Isso faz sentido para você?
AK
2
@Leigh: Usando COALESCE()como isso não vai distinguir o ( 0soma) de um conjunto vazio do ( NULLsoma) (dizem que a mesa tinha uma (10, NULL)linha.
ypercubeᵀᴹ
Além disso, ainda não podemos distinguir SUM (conjunto vazio) de SUM (conjunto de um ou mais NULLs). Precisamos distinguir?
AK
@AlexKuznetsov - Podemos distinguir a soma de um conjunto vazio da soma de um conjunto que contém um ou mais valores nulos, desde que pelo menos uma linha contenha um valor. Você está certo de que, se o conjunto contiver apenas NULLs, não será possível distinguir o conjunto NULL deste conjunto de todos os valores NULL. Meu argumento não era que fosse útil em todos os casos, apenas que pudesse ser útil. Se eu SUMcoluna e volto a zero, sei sem precisar verificar se há pelo menos uma linha que não seja NULL sendo usada para me mostrar o resultado.
Leigh Riffel
@ypercude - Você está absolutamente correto. Meu argumento foi que o comportamento atual do SUM distingue um conjunto vazio de um conjunto que contém valores (mesmo que alguns sejam nulos). É mais simples usar COALESCE quando a distinção não é necessária do que usar algo como DECODE(count(c2),0,NULL,sum(c2))quando é.
Leigh Riffel
-1

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 visualizando. Seu tipo de retorno deve ser compatível com todos os membros do conjunto (pense em float, moeda, decimal, bcd, período de tempo, ...). Como não existe um conjunto, você não pode sugerir um tipo de retorno, portanto, NULL é sua melhor opção.

Nota: Na maioria dos casos, você pode sugerir um tipo de retorno do tipo de coluna que está visualizando, mas é possível fazer SUMs não apenas nas colunas, mas em todos os tipos de coisas. Implicar um tipo de retorno pode ficar muito difícil, se não impossível, sob certas circunstâncias, especialmente quando você pensa em possíveis expansões do padrão (tipos dinâmicos vêm à mente).

TToni
fonte
5
Por que não podemos implicar um tipo de retorno em uma SUM(column)expressão? Não temos tabelas vazias - e todas as colunas têm tipos definidos? Por que deveria ser diferente para um conjunto de resultados vazio?
precisa saber é o seguinte
5
Você errar onde você diz "uma vez que há NO SET ". Existe um conjunto. O conjunto de todos os valores possíveis do tipo declarado das colunas ou expressões envolvidas. Esse tipo declarado existe mesmo que a tabela que você está vendo esteja vazia. Mesas vazias ainda têm um cabeçalho. E esse tipo declarado é exatamente o seu "tipo de retorno implícito".
Erwin Smout 5/10
Vocês realmente leram minha nota? Sim, funcionaria para SUMs baseadas em colunas a partir de agora. Porém, assim que encontrar uma coluna de tipo de dados variável (ainda não no SQL Server), você estará sem sorte.
TToni
2
Como você definirá a soma nesse caso? Qual será o resultado 24 + 56.07 + '2012-10-05' + 'Red'? Quero dizer, não há nenhuma preocupação em como SUM()se comportará quando tivermos um problema ao definir a adição.
usar o seguinte código
1
@TToni: "especialmente quando você pensa em possíveis expansões do padrão" não é o contexto ao qual o OP estava se referindo. o OP estava se referindo muito claramente à versão atual do padrão, que não inclui nenhum tipo de noção de "tipos dinâmicos" ou algo parecido. (. Oh, e eu só comentou, mas não downvote Além disso pequeno pedaço I teve problema com, nada na sua resposta foi suficiente errado para justificar uma downvote IMO..)
Erwin Smout