Estou atualizando meu IDENTITY
script de verificação de estouro para contabilizar DECIMAL
e NUMERIC
IDENTITY
colunas .
Como parte da verificação, calculo o tamanho do intervalo do tipo de dados para cada IDENTITY
coluna; Eu uso isso para calcular qual porcentagem desse intervalo foi esgotada. Pois DECIMAL
e NUMERIC
o tamanho desse intervalo é2 * 10^p - 2
onde p
está a precisão.
Criei várias tabelas de teste com colunas DECIMAL
e NUMERIC
IDENTITY
e tentei calcular seus intervalos da seguinte maneira:
SELECT POWER(10.0, precision)
FROM sys.columns
WHERE
is_identity = 1
AND type_is_decimal_or_numeric
;
Isso lançou o seguinte erro:
Msg 8115, Level 16, State 6, Line 1
Arithmetic overflow error converting float to data type numeric.
Eu reduzi para as IDENTITY
colunas do tipo DECIMAL(38, 0)
(ou seja, com a precisão máxima), então tentei o POWER()
cálculo diretamente naquele valor.
Todas as consultas a seguir
SELECT POWER(10.0, 38.0);
SELECT CONVERT(FLOAT, (POWER(10.0, 38.0)));
SELECT CAST(POWER(10.0, 38.0) AS FLOAT);
também resultou no mesmo erro.
- Por que o SQL Server tenta converter a saída de
POWER()
, que é do tipoFLOAT
, paraNUMERIC
(especialmente quandoFLOAT
tem uma precedência mais alta )? - Como posso calcular dinamicamente o intervalo de uma coluna
DECIMAL
ou para todas as precisões possíveis (incluindo , é claro)?NUMERIC
p = 38
Em vez de me intrometer mais na resposta de Martin, adicionarei o restante de minhas descobertas
POWER()
aqui.Segure-se em sua calcinha.
Preâmbulo
Primeiro, apresento a você o anexo A, a documentação do MSDN para
POWER()
:Você pode concluir lendo a última linha que
POWER()
o tipo de retorno éFLOAT
, mas leia novamente.float_expression
é "do tipo float ou de um tipo que pode ser implicitamente convertido em float". Portanto, apesar do nome,float_expression
pode ser na verdade aFLOAT
, aDECIMAL
ou anINT
. Como a saída dePOWER()
é a mesma defloat_expression
, também pode ser um desses tipos.Portanto, temos uma função escalar com tipos de retorno que dependem da entrada. Poderia ser?
Observações
Eu apresento a você a demonstração B, um teste demonstrando que
POWER()
converte sua saída para diferentes tipos de dados, dependendo de sua entrada .Os resultados relevantes são:
O que parece estar acontecendo é que
POWER()
lançafloat_expression
no menor tipo que se encaixa, não incluindoBIGINT
.Portanto,
SELECT POWER(10.0, 38);
falha com um erro de estouro porque10.0
é convertido para oNUMERIC(38, 1)
qual não é grande o suficiente para conter o resultado de 10 38 . Isso ocorre porque 10 38 se expande para receber 39 dígitos antes do decimal, enquantoNUMERIC(38, 1)
pode armazenar 37 dígitos antes do decimal mais um depois dele. Portanto, o valor máximo queNUMERIC(38, 1)
pode conter é 10 37 - 0,1.Armado com esse entendimento, posso inventar outra falha de estouro da seguinte maneira.
Um bilhão (ao contrário do trilhão do primeiro exemplo, que é convertido para
NUMERIC(38, 0)
) é pequeno o suficiente para caber em umINT
. Um bilhão elevado à terceira potência, no entanto, é muito grande paraINT
, daí o erro de estouro.Várias outras funções exibem comportamento semelhante, onde seu tipo de saída depende de sua entrada:
POWER()
,CEILING()
,FLOOR()
,RADIANS()
,DEGREES()
, eABS()
NULLIF()
,ISNULL()
,COALESCE()
,IIF()
,CHOOSE()
eCASE
expressõesSELECT 2 * @MAX_INT;
eSELECT @MAX_SMALLINT + @MAX_SMALLINT;
, por exemplo, resultam em estouros aritméticos quando as variáveis são do tipo de dados nomeado.Conclusão
Neste caso particular, a solução é usar
SELECT POWER(1e1, precision)...
. Isso funcionará para todas as precisões possíveis, pois1e1
é convertido paraFLOAT
, que pode conter números ridiculamente grandes .Como essas funções são tão comuns, é importante entender que seus resultados podem ser arredondados ou causar erros de estouro devido ao seu comportamento. Se você espera ou depende de um tipo de dados específico para sua saída, converta explicitamente a entrada relevante conforme necessário.
Então, crianças, agora que vocês sabem disso, podem seguir em frente e prosperar.
Da
POWER
documentação :A primeira entrada é convertida implicitamente,
float
se necessário.O cálculo interno é executado usando
float
aritmética pela função C Runtime Library (CRT) padrãopow
.A
float
saída depow
é, então, convertida de volta para o tipo do operando esquerdo (supostamentenumeric(3,1)
quando você usa o valor literal 10.0).Usar um explícito
float
funciona bem no seu caso:Um resultado exato para 10 38 não pode ser armazenado em um SQL Server
decimal/numeric
porque exigiria 39 dígitos de precisão (1 seguido de 38 zeros). A precisão máxima é 38.Me deparei com esta postagem enquanto investigava erros de estouro enquanto atualizava meu próprio script de verificação de estouro de identidade :)
A resposta aceita de Martin Smith aqui e outra resposta dele em https://stackoverflow.com/a/5663463/1508467 fornecem as informações técnicas, mas posso adicionar uma abordagem alternativa para a segunda parte da pergunta - como criar o min/max valor para uma coluna decimal.
Use a precisão e a escala com a função REPLICATE de string para criar uma string de 9s e convertê-la em um decimal adequado.
Para colunas de identidade decimal/numérica, a escala deve ser zero para que o acima possa ser simplificado ainda mais.