我想出了以下解决方案:
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) 或 NULL)。
它被硬编码为decimal(38,10)类型,所以如果你需要不同的精度,请更改代码中的常量(38、10、29)。
它是如何工作的?结果是:
- 如果转换简单且没有溢出或精度损失(例如 123 或 123.456),则只需转换即可。
- 如果数字不是太大,但小数点后的位数太多(例如 123.1234567890123456789012345678901234567890),那么它会在末尾修剪超出的位数,只保留前 38 位。
- 如果数字太大并且不能转换为十进制而不溢出(例如9876543210987654321098765432109876543210),则返回NULL
在上面的代码中,每种情况都是单独的 WHEN 语句。
以下是几个转化示例: