StringToDecimal函数 - T-SQL问题

时间:2011-02-15 15:28:22

标签: sql-server tsql

我有一些脏输入数据被导入到SQL Server(2008 R2)中的原始源表中。输入提供程序定义为十进制(9,2)或十进制(4,2)的字段以字符串形式出现,但字符串并不总是符合数据定义(如图所示)。

我们将平面文件中的数据导入到原始表中,然后应用一些转换脚本将“已清理”的数据插入到表中,并将相应的数据类型分配给列。

例如:

raw_table
TotalAmount varchar(12)

clean_table
TotalAmount decimal(9,2)

现在,我的问题是这个。如果我想对此进行一些“基本”清理,我想在一个函数中执行以下操作:

CREATE FUNCTION [dbo].[StringToDecimal]
(
    @conversionString VARCHAR(12)
)   
RETURNS DECIMAL(9,2)
AS
BEGIN   

    DECLARE @rsp DECIMAL(9,2)

    IF ISNUMERIC( LTRIM(RTRIM(REPLACE(@conversionString,' ',''))) ) = 1
         BEGIN
             SET @rsp = ISNULL( CONVERT( decimal(17,6), NULLIF( LTRIM(RTRIM(REPLACE(@conversionString,' ',''))),'') ), 0 )
         END
    ELSE
         BEGIN
             SET @rsp = 0 -- or we can return NULL here
         END

    RETURN @rsp
END

然而,如何在这种混合中支持各种大小的小数?有没有办法参数化响应类型?我考虑只返回我们通常看到的最大尺寸的小数,然后在另一端再次转换它,但是,你会遇到算术溢出问题。

感谢您对解决这个问题的任何想法/见解!

2 个答案:

答案 0 :(得分:2)

  

有没有办法对参数类型进行参数化?

这比你想象的要简单。只需返回VARCHAR并从VARCHAR转换为十进制(x,y)。您甚至不需要需要 - 您可以直接将VARCHAR(只要它包含有效的十进制数据)分配给十进制列/变量。

我将创建2个函数。 StringToDecimal2执行实际转换,但返回6个“错误代码”之一。您可以使用它来检查why字符串是否无效。或者使用包装器dbo.StringToDecimal,它只是将无效代码转换为NULL。

CREATE FUNCTION [dbo].[StringToDecimal2]
(
    @conversionString VARCHAR(12),
    @precision int,  -- total digits
    @scale int  -- after decimal point
)   
RETURNS VARCHAR(100)
AS
BEGIN
    -- remove spaces, we'll allow this error. no need to trim
    set @conversionString = REPLACE(@conversionString,' ','')
    -- note: 1,234.56 (thousands separated) will be invalid, so will 1,234,56 (European decimals)
    -- well, ok, let's clean up the thousands separators. BUT! It will incorrectly scale European decimals
    set @conversionString = REPLACE(@conversionString,',','')

    -- we don't support scientific notation either, so 1e4 (10,000) is out

    if @conversionString like '%[^0-9.+-]%' return 'INVALID1' -- only digits and decimal are valid (plus +-)
    if @conversionString like '%.%.%' return 'INVALID2' -- too many decimals
    if @conversionString like '_%[+-]%' return 'INVALID3' -- +- symbol not in the first position
    if @conversionString like '[.+-]' return 'INVALID4' -- a single character from "+-."
    if @conversionString like '[+-].' return 'INVALID5' -- symbol and decimal only

    -- add a decimal place so it is easier to work with below
    if @conversionString not like '%.%'
        set @conversionString = @conversionString + '.'

    -- allow decimal places to go only as far as scale
    set @conversionString = left(@conversionString, charindex('.', @conversionString)+@scale)

    -- ensure the data is within precision number of digits in total
    if charindex('.', @conversionString) > @precision - @scale + 1
        return 'INVALID6' -- too many digits before decimal

    RETURN @conversionString
END
GO

CREATE FUNCTION [dbo].[StringToDecimal]
(
    @conversionString VARCHAR(12),
    @precision int,  -- total digits
    @scale int  -- after decimal point
)
RETURNS VARCHAR(100)
AS
BEGIN
RETURN case when [dbo].[StringToDecimal2](@conversionString, @precision, @scale) like 'INVALID%'
then null else [dbo].[StringToDecimal2](@conversionString, @precision, @scale) end
END
GO

一些测试:

select [dbo].[StringToDecimal2]('12342342', 9,2)

select convert(decimal(9,2),[dbo].[StringToDecimal]('1234234', 9,2))
select convert(decimal(9,2),[dbo].[StringToDecimal]('12342342', 9,2))
select convert(decimal(9,2),[dbo].[StringToDecimal]('123423.3333', 9,2))
select convert(decimal(20,10),[dbo].[StringToDecimal]('123423sd.3333', 20,10))
select convert(decimal(20,10),[dbo].[StringToDecimal]('123423sd..3333', 20,10))
select convert(decimal(20,10),[dbo].[StringToDecimal]('-123423.3333', 20,10))
select convert(decimal(20,10),[dbo].[StringToDecimal]('+123423..3333', 20,10))

答案 1 :(得分:0)

感谢您提供额外信息。听起来你有三个步骤:

  1. 从字符串中删除非数字或小数点的所有字符(您是否在一个字符串中获得多个点?)
  2. 视情况转换为(9,5)或(4,1)(你如何判断?是否存在舍入?10X.781是10.78100还是10.7或10.8?)
  3. 在某处插入/更新最终值
  4. 仅基于第1点,我会立即避免使用TSQL并考虑外部脚本或CLR过程。 CLR函数可以进行解析,但是仍然存在返回不同数据类型的问题。

    由于这似乎是某种ETL任务,在我的环境中,我可能会将其作为SSIS包中的脚本组件实现。该组件将进行解析并将干净数据发送到不同的输出以进行进一步处理。如果它是一次性任务,我将使用Python脚本来解析输入数据并生成INSERT或UPDATE语句。

    我不知道这些解决方案是否适合您,但也许它会给您一些想法。你应该避免使用ISNUMERIC()函数;搜索此网站或Google以查找一些它认为是数字的“奇怪”输入。