VARCHAR到DECIMAL

Bij*_*mas 24 sql sql-server sql-server-2008

我想将varchar(max)列转换为decimal(10,4).

当我尝试使用castconvert我得到算术溢出异常.问题是存储在varchar列中的数据可能包含不同的精度和不同的比例.例如,123456789.1234567',1.12345678或123456.1234.

对于像123456.1234这样的值,它会转换出任何问题,但对于其他值,我遇到了一些问题.

任何帮助,将不胜感激.

cod*_*biz 23

测试后我发现它不是导致问题的小数位,它是精度(10)

这不起作用:将varchar转换为数据类型numeric的算术溢出错误.

DECLARE @TestConvert VARCHAR(MAX) = '123456789.12343594'

SELECT CAST(@TestConvert AS DECIMAL(10, 4))
Run Code Online (Sandbox Code Playgroud)

这很有效

DECLARE @TestConvert VARCHAR(MAX) = '123456789.12343594'

SELECT CAST(@TestConvert AS DECIMAL(18, 4))
Run Code Online (Sandbox Code Playgroud)


Jer*_*ore 6

我的解释在代码中。:)

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)))
Run Code Online (Sandbox Code Playgroud)


Mar*_*ram 5

您还没有解释为什么不能使用 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)
Run Code Online (Sandbox Code Playgroud)


小智 5

您忽略了 6.999,50 不是有效小数这一事实。小数值中肯定不能有逗号和小数点吗?应该是多少?

假设您的区域设置指定 . 作为分组和 , 作为小数点分隔符:要删除分组数字:

选择转换(十进制(11,2),替换('6.999,50','.',''))

将产生小数 6999,50


Sas*_*sha 5

我想出了以下解决方案:

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
Run Code Online (Sandbox Code Playgroud)

我知道这看起来有点矫枉过正,可能确实如此,但它对我有用(检查了不同精度和比例的正数、负数、大数和小数 - 一切都转换为decimal(38,10)or NULL)。

它是硬编码的decimal(38,10),因此如果您需要不同的精度,请更改代码中的常量 (38, 10, 29)。

这个怎么运作?结果是

  • 如果转换很简单,没有溢出或精度损失(例如 123 或 123.456),那么它只是转换它。
  • 如果数字不是太大,但小数点后有太多数字(例如 123.1234567890123456789012345678901234567890),那么它会修剪末尾的多余数字,只保留前 38 位数字。
  • 如果数字太大并且无法在没有溢出的情况下转换为十进制(例如 9876543210987654321098765432109876543210),则返回 NULL

在上面的代码中,每种情况都是单独的 WHEN 语句。

以下是几个转换示例: 在此处输入图片说明


Bij*_*mas 3

使用自定义函数实现。这将检查字符串值是否可以安全地转换为十进制

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
Run Code Online (Sandbox Code Playgroud)