我想将varchar(max)
列转换为decimal(10,4)
。
当我尝试使用cast
或convert
时,我收到了算术溢出异常。问题是存储在varchar列中的数据可能包含不同的精度和不同的比例。例如,123456789.1234567',1.12345678或123456.1234。
对于像123456.1234这样的值,它会转换出任何问题,但对于其他值,我遇到了一些问题。
任何帮助都将不胜感激。
答案 0 :(得分:23)
经过测试,我发现它不是导致问题的小数位,它是精度(10)
这不起作用:将varchar转换为数据类型numeric的算术溢出错误。
DECLARE @TestConvert VARCHAR(MAX) = '123456789.12343594'
SELECT CAST(@TestConvert AS DECIMAL(10, 4))
这有效
DECLARE @TestConvert VARCHAR(MAX) = '123456789.12343594'
SELECT CAST(@TestConvert AS DECIMAL(18, 4))
答案 1 :(得分:4)
我的解释在代码中。 :)
DECLARE @TestConvert VARCHAR(MAX) = '123456789.1234567'
BEGIN TRY
SELECT CAST(@TestConvert AS DECIMAL(10, 4))
END TRY
BEGIN CATCH
SELECT 'The reason you get the message "' + ERROR_MESSAGE() + '" is because DECIMAL(10, 4) only allows for 4 numbers after the decimal.'
END CATCH
-- Here's one way to truncate the string to a castable value.
SELECT CAST(LEFT(@TestConvert, (CHARINDEX('.', @TestConvert, 1) + 4)) AS DECIMAL(14, 4))
-- If you noticed, I changed it to DECIMAL(14, 4) instead of DECIMAL(10, 4) That's because this number has 14 digits, as proven below.
-- Read this for a better explanation as to what precision, scale and length mean: http://msdn.microsoft.com/en-us/library/ms190476(v=sql.105).aspx
SELECT LEN(LEFT(@TestConvert, (CHARINDEX('.', @TestConvert, 1) + 4)))
答案 2 :(得分:4)
您错过了6.999,50不是有效小数的事实。肯定不能有十进制值的逗号和小数点吗?它应该是多少?
假设您的语言环境指定。作为分组和小数分隔符:删除分组数字:
SELECT CONVERT(十进制(11,2),REPLACE('6.999,50','。',''))
将产生6999,50作为小数
答案 3 :(得分:3)
在将值放入该列之前,您必须自己将值截断为字符串。
否则,如果您想要更多小数位,则需要更改小数列的声明。
答案 4 :(得分:3)
我提出了以下解决方案:
SELECT [Str], DecimalParsed = CASE
WHEN ISNUMERIC([Str]) = 1 AND CHARINDEX('.', [Str])=0 AND LEN(REPLACE(REPLACE([Str], '-', ''), '+', '')) < 29 THEN CONVERT(decimal(38,10), [Str])
WHEN ISNUMERIC([Str]) = 1 AND (CHARINDEX('.', [Str])!=0 AND CHARINDEX('.', REPLACE(REPLACE([Str], '-', ''), '+', ''))<=29) THEN
CONVERT(decimal(38,10),
CASE WHEN LEN([Str]) - LEN(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE([Str], '0', ''), '1', ''), '2', ''), '3', ''), '4', ''), '5', ''), '6', ''), '7', ''), '8', ''), '9', '')) <= 38
THEN [Str]
ELSE SUBSTRING([Str], 1, 38 + LEN(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE([Str], '0', ''), '1', ''), '2', ''), '3', ''), '4', ''), '5', ''), '6', ''), '7', ''), '8', ''), '9', ''))) END)
ELSE NULL END
FROM TestStrToDecimal
我知道它看起来像是一种矫枉过正,可能是它,但它适用于我(检查正面,负面,大小不同的精度和比例 - 一切都转换为decimal(38,10)
或{{1 }})。
它被硬编码为NULL
类型,因此如果您需要不同的精度,请更改代码中的常量(38,10,29)。
它如何运作?结果是:
每个案例都是上面代码中单独的WHEN语句。
以下是转化的几个例子:
答案 5 :(得分:2)
你仍然没有解释为什么你不能使用Float数据类型,所以这是一个例子:
DECLARE @StringVal varchar(50)
SET @StringVal = '123456789.1234567'
SELECT @StringVal, CAST(@StringVal AS FLOAT)
SET @StringVal = '1.12345678'
SELECT @StringVal, CAST(@StringVal AS FLOAT)
SET @StringVal = '123456.1234'
SELECT @StringVal, CAST(@StringVal AS FLOAT)
答案 6 :(得分:2)
你的主要问题不是小数点右边的东西,而是左边的东西。类型声明中的两个值是精度和比例。
来自MSDN:“精确度是一个数字中的位数。比例是 数字中小数点右边的位数。 例如,数字123.45的精度为5,标度为2.“
如果指定(10,4),则表示您只能在小数点左侧存储6位数字,或者最多存储999999.9999。任何比这更大的东西都会导致溢出。
答案 7 :(得分:1)
使用自定义函数实现。 这将检查字符串值是否可以安全地转换为Decimal
CREATE FUNCTION [dbo].[TryParseAsDecimal]
(
@Value NVARCHAR(4000)
,@Precision INT
,@Scale INT
)
RETURNS BIT
AS
BEGIN
IF(ISNUMERIC(@Value) =0) BEGIN
RETURN CAST(0 AS BIT)
END
SELECT @Value = REPLACE(@Value,',','') --Removes the comma
--This function validates only the first part eg '1234567.8901111111'
--It validates only the values before the '.' ie '1234567.'
DECLARE @Index INT
DECLARE @Part1Length INT
DECLARE @Part1 VARCHAR(4000)
SELECT @Index = CHARINDEX('.', @Value, 0)
IF (@Index>0) BEGIN
--If decimal places, extract the left part only and cast it to avoid leading zeros (eg.'0000000001' => '1')
SELECT @Part1 =LEFT(@Value, @Index-1);
SELECT @Part1=SUBSTRING(@Part1, PATINDEX('%[^0]%', @Part1+'.'), LEN(@Part1));
SELECT @Part1Length = LEN(@Part1);
END
ELSE BEGIN
SELECT @Part1 =CAST(@Value AS DECIMAL);
SELECT @Part1Length= LEN(@Part1)
END
IF (@Part1Length > (@Precision-@Scale)) BEGIN
RETURN CAST(0 AS BIT)
END
RETURN CAST(1 AS BIT)
END
答案 8 :(得分:1)
我知道这是一个老问题,但比尔似乎是唯一一个实际上“解释”这个问题的人。其他人似乎都在提出滥用声明的复杂解决方案。
“类型声明中的两个值是精度和比例。”
...
“如果指定(10,4),则表示您只能存储6位数字 小数点左边,或最大数字999999.9999。更大的东西 比那会导致溢出。“
因此,如果您声明DECIMAL(10,4)
,您可以总共有 10 个数字,其中 4 来自小数点后。
所以123456.1234有10位数,小数点后4位。这将适合DECIMAL(10,4)
的参数。
1234567.1234将抛出错误。有10位数字可以容纳10位数字,小数点后必须使用4位数字。修剪小数左侧的数字不是一个选项。
如果你的11个字符是123456.12345,这不会引起错误,因为从十进制值的末尾开始修剪(舍入)是可以接受的。
在声明小数时,请始终尝试声明列实际使用的最大值以及要查看的最大小数位数。
因此,如果您的列只显示最大值为1百万的值,而您只关心前两位小数,则声明为DECIMAL(9,2)
。
在抛出错误之前,这将为您提供最大数量9,999,999.99。
在尝试修复问题之前了解问题,将确保为您的情况选择正确的修复方法,并帮助您了解修复所需的原因/工作原因。
同样,我知道我迟到了五年。
但是,我对此解决方案的两分钱(根据您的评论判断该列已设置为DECIMAL(10,4)
且无法更改)
最简单的方法是两个步骤。
检查您的小数是否超过10分,然后修剪为10位。
CASE WHEN CHARINDEX('.',CONVERT(VARCHAR(50),[columnName]))>10 THEN 'DealWithIt'
ELSE LEFT(CONVERT(VARCHAR(50),[columnName]),10)
END AS [10PointDecimalString]
我把它作为字符串保留的原因是你可以处理小数点左边超过10位数的值。
但它是一个开始。
答案 9 :(得分:0)
如果您需要舍入结果而不是截断,可以使用以下方法:
选择convert(decimal(38,4),round(convert(decimal(38,10),'123456789.1234567'),4))
这将返回: '123456789.1234567'的'123456789.1234567' '123456789.1234467'的'123456789.1234'
答案 10 :(得分:0)
在MySQL中
select convert( if( listPrice REGEXP '^[0-9]+$', listPrice, '0' ), DECIMAL(15, 3) ) from MyProduct WHERE 1
答案 11 :(得分:0)
create function [Sistema].[fParseDecimal]
(
@Valor nvarchar(4000)
)
returns decimal(18, 4) as begin
declare @Valores table (Valor varchar(50));
insert into @Valores values (@Valor);
declare @Resultado decimal(18, 4) = (select top 1
cast('' as xml).value('sql:column("Valor") cast as xs:decimal ?', 'decimal(18, 4)')
from @Valores);
return @Resultado;
END