SQL Server 2008 R2的Try_Convert函数

34
我正在使用 SQL Server 2008 R2,有一个 VARCHAR 列需要使用 CONVERT 转换为 DECIMAL(28,10)。但是很多行的格式非常糟糕,无法将它们解析为数字。在这种情况下,我只想跳过它们,并将结果设置为0或NULL。

我知道 SQL Server 2012 中有一个新语句(TRY_CONVERT()),非常方便。

在 SQL Server 2008 中是否可能实现此操作,还是必须等到我们更新到下一个版本的 SQL 2012?

编辑

不幸的是,在这种情况下ISNUMERIC()不太可靠。我已经尝试了

ISNUMERIC(myCol) = 1

对于 CONVERT 无法转换为 DECIMAL 的行,返回 true。


请勿重复:https://dev59.com/8nI-5IYBdhLWcg3wI0t9#2000061 - Maxim Zhukov
你想要永久地转换表中的列,还是只在结果集中转换? - Damien_The_Unbeliever
可能是重复的问题:TSQL - 将字符串转换为整数或返回默认值 - Preet Sangha
@Damien_The_Unbeliever,我想从一个varchar列更新一个永久的decimal列。 - Roland Bengtsson
你检查了我的答案吗? - JotaPardo
显示剩余2条评论
8个回答

51

在 SQL Server 中使用 XML 时,您可以尝试将其转换为数据类型,如果转换失败,则会收到 null 值。

declare @T table
(
  Col varchar(50)
)

insert into @T values
('1'),
('1.1'),
('1,1'),
('1a')

select cast('' as xml).value('sql:column("Col") cast as xs:decimal ?', 
                             'decimal(28,10)') as Col
from @T

结果:

Col
-------------
1.0000000000
1.1000000000
NULL
NULL

6

既然这是一个永久性的更改,那么我会将其作为一个两步过程完成 - 首先删除无效文本,然后转换列。

要删除无效文本,我会执行以下操作:

UPDATE [Table]
SET [Column] = NULL
WHERE [Column] LIKE '%[^0-9.]%' or
LEN([Column]) - LEN(REPLACE([Column],'.','')) > 1 or
LEN([Column]) > 28

完成这一步之后,只需要更改列定义,剩下的所有内容都可以转换。
ALTER TABLE [Table] ALTER COLUMN [Column] decimal(28,10)

1
不幸的是,我不想写入varchar列。即使数据无法解析,它可能包含我不想删除的注释等内容。我知道将注释和数字存储在同一列中的设计非常错误... - Roland Bengtsson
@RolandBengtsson - 啊,所以你想要(比如说)一个计算列,如果可以进行转换,它包含转换为十进制的值?我最初理解你的问题是替换现有列。 - Damien_The_Unbeliever

6

我编写了一个有用的标量函数,用于在SQL Server 2008中模拟SQL SERVER 2012的TRY_CAST函数。

dbo.TRY_CAST(Expression, Data_Type, ReturnValueIfErrorCast)

TRY_CAST函数在SQL Server 2012中有两个主要的区别,你必须传递3个参数,并且你还必须显式地将转换或转型应用到字段上。然而,它仍然非常有用,因为它允许你在转型未能正确执行时返回一个默认值。

函数代码:

DECLARE @strSQL NVARCHAR(1000)
IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[TRY_CAST]'))
    BEGIN
        SET @strSQL = 'CREATE FUNCTION [dbo].[TRY_CAST] () RETURNS INT AS BEGIN RETURN 0 END'
        EXEC sys.sp_executesql @strSQL
    END

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

/*
------------------------------------------------------------------------------------------------------------------------
    Description:    
                    Syntax 
                    ---------------
                    dbo.TRY_CAST(Expression, Data_Type, ReturnValueIfErrorCast)

                    +---------------------------+-----------------------+
                    |   Expression              |   VARCHAR(8000)       |
                    +---------------------------+-----------------------+
                    |   Data_Type               |   VARCHAR(8000)       |
                    +---------------------------+-----------------------+
                    |   ReturnValueIfErrorCast  |   SQL_VARIANT = NULL  |
                    +---------------------------+-----------------------+


                    Arguments
                    ---------------
                    expression
                    The value to be cast. Any valid expression.

                    Data_Type
                    The data type into which to cast expression.

                    ReturnValueIfErrorCast
                    Value returned if cast fails or is not supported. Required. Set the DEFAULT value by default.


                    Return Type
                    ----------------
                    Returns value cast to SQL_VARIANT type if the cast succeeds; otherwise, returns null if the parameter @pReturnValueIfErrorCast is set to DEFAULT, 
                    or that the user indicates.


                    Remarks
                    ----------------
                    dbo.TRY_CAST function simulates the TRY_CAST function reserved of SQL SERVER 2012 for using in SQL SERVER 2008. 
                    dbo.TRY_CAST function takes the value passed to it and tries to convert it to the specified Data_Type. 
                    If the cast succeeds, dbo.TRY_CAST returns the value as SQL_VARIANT type; if the cast doesn´t succees, null is returned if the parameter @pReturnValueIfErrorCast is set to DEFAULT. 
                    If the Data_Type is unsupported will return @pReturnValueIfErrorCast.
                    dbo.TRY_CAST function requires user make an explicit CAST or CONVERT in ANY statements.
                    This version of dbo.TRY_CAST only supports CAST for INT, DATE, NUMERIC and BIT types.


                    Examples
                    ====================================================================================================

                    --A. Test TRY_CAST function returns null

                        SELECT   
                            CASE WHEN dbo.TRY_CAST('6666666166666212', 'INT', DEFAULT) IS NULL   
                            THEN 'Cast failed'  
                            ELSE 'Cast succeeded'  
                        END AS Result; 

                    GO

                    --B. Error Cast With User Value

                        SELECT   
                            dbo.TRY_CAST('2147483648', 'INT', DEFAULT) AS [Error Cast With DEFAULT],
                            dbo.TRY_CAST('2147483648', 'INT', -1) AS [Error Cast With User Value],
                            dbo.TRY_CAST('2147483648', 'INT', NULL) AS [Error Cast With User NULL Value]; 

                        GO 

                    --C. Additional CAST or CONVERT required in any assignment statement

                        DECLARE @IntegerVariable AS INT

                        SET @IntegerVariable = CAST(dbo.TRY_CAST(123, 'INT', DEFAULT) AS INT)

                        SELECT @IntegerVariable

                        GO 

                        IF OBJECT_ID('tempdb..#temp') IS NOT NULL
                            DROP TABLE #temp

                        CREATE TABLE #temp (
                            Id INT IDENTITY
                            , FieldNumeric NUMERIC(3, 1)
                            )

                        INSERT INTO dbo.#temp (FieldNumeric)
                        SELECT CAST(dbo.TRY_CAST(12.3, 'NUMERIC(3,1)', 0) AS NUMERIC(3, 1));--Need explicit CAST on INSERT statements

                        SELECT *
                        FROM #temp

                        DROP TABLE #temp

                        GO 

                    --D. Supports CAST for INT, DATE, NUMERIC and BIT types.

                        SELECT dbo.TRY_CAST(2147483648, 'INT', 0) AS [Cast failed]
                            , dbo.TRY_CAST(2147483647, 'INT', 0) AS [Cast succeeded]
                            , SQL_VARIANT_PROPERTY(dbo.TRY_CAST(212, 'INT', 0), 'BaseType') AS [BaseType];

                        SELECT dbo.TRY_CAST('AAAA0101', 'DATE', DEFAULT) AS [Cast failed]
                            , dbo.TRY_CAST('20160101', 'DATE', DEFAULT) AS [Cast succeeded]
                            , SQL_VARIANT_PROPERTY(dbo.TRY_CAST('2016-01-01', 'DATE', DEFAULT), 'BaseType') AS [BaseType];

                        SELECT dbo.TRY_CAST(1.23, 'NUMERIC(3,1)', DEFAULT) AS [Cast failed]
                            , dbo.TRY_CAST(12.3, 'NUMERIC(3,1)', DEFAULT) AS [Cast succeeded]
                            , SQL_VARIANT_PROPERTY(dbo.TRY_CAST(12.3, 'NUMERIC(3,1)', DEFAULT), 'BaseType') AS [BaseType];

                        SELECT dbo.TRY_CAST('A', 'BIT', DEFAULT) AS [Cast failed]
                            , dbo.TRY_CAST(1, 'BIT', DEFAULT) AS [Cast succeeded]
                            , SQL_VARIANT_PROPERTY(dbo.TRY_CAST('123', 'BIT', DEFAULT), 'BaseType') AS [BaseType];

                        GO 

                    --E. B. TRY_CAST return NULL on unsupported data_types

                        SELECT dbo.TRY_CAST(4, 'xml', DEFAULT) AS [unsupported];  

                        GO  

                    ====================================================================================================

------------------------------------------------------------------------------------------------------------------------
    Responsible:    Javier Pardo 
    Date:           diciembre 29/2016
    WB tests:       Javier Pardo 
------------------------------------------------------------------------------------------------------------------------
    Update by:      Javier Eduardo Pardo Moreno 
    Date:           febrero 16/2017
    Id update:      JEPM20170216
    Description:    Fix  ISNUMERIC function makes it unreliable. SELECT dbo.TRY_CAST('+', 'INT', 0) will yield Msg 8114, 
                    Level 16, State 5, Line 16 Error converting data type varchar to float.
                    ISNUMERIC() function treats few more characters as numeric, like: – (minus), + (plus), $ (dollar), \ (back slash), (.)dot and (,)comma
                    Collaborator aperiooculus (http://stackoverflow.com/users/3083382/aperiooculus )

                    Fix dbo.TRY_CAST('2013/09/20', 'datetime', DEFAULT) for supporting DATETIME format

    WB tests:       Javier Pardo

------------------------------------------------------------------------------------------------------------------------
*/

ALTER FUNCTION dbo.TRY_CAST
(
    @pExpression AS VARCHAR(8000),
    @pData_Type AS VARCHAR(8000),
    @pReturnValueIfErrorCast AS SQL_VARIANT = NULL
)
RETURNS SQL_VARIANT
AS
BEGIN
    --------------------------------------------------------------------------------
    --  INT 
    --------------------------------------------------------------------------------

    IF @pData_Type = 'INT'
    BEGIN
        IF ISNUMERIC(@pExpression) = 1 AND @pExpression NOT IN ('-','+','$','.',',','\')    --JEPM20170216
        BEGIN
            DECLARE @pExpressionINT AS FLOAT = CAST(@pExpression AS FLOAT)

            IF @pExpressionINT BETWEEN - 2147483648.0 AND 2147483647.0
            BEGIN
                RETURN CAST(@pExpressionINT as INT)
            END
            ELSE
            BEGIN
                RETURN @pReturnValueIfErrorCast
            END --FIN IF @pExpressionINT BETWEEN - 2147483648.0 AND 2147483647.0
        END
        ELSE
        BEGIN
            RETURN @pReturnValueIfErrorCast
        END -- FIN IF ISNUMERIC(@pExpression) = 1
    END -- FIN IF @pData_Type = 'INT'

    --------------------------------------------------------------------------------
    --  DATE    
    --------------------------------------------------------------------------------

    IF @pData_Type IN ('DATE','DATETIME')
    BEGIN
        IF ISDATE(@pExpression) = 1
        BEGIN

            DECLARE @pExpressionDATE AS DATETIME = cast(@pExpression AS DATETIME)

            IF @pData_Type = 'DATE'
            BEGIN
                RETURN cast(@pExpressionDATE as DATE)
            END

            IF @pData_Type = 'DATETIME'
            BEGIN
                RETURN cast(@pExpressionDATE as DATETIME)
            END

        END
        ELSE 
        BEGIN

            DECLARE @pExpressionDATEReplaced AS VARCHAR(50) = REPLACE(REPLACE(REPLACE(@pExpression,'\',''),'/',''),'-','')

            IF ISDATE(@pExpressionDATEReplaced) = 1
            BEGIN
                IF @pData_Type = 'DATE'
                BEGIN
                    RETURN cast(@pExpressionDATEReplaced as DATE)
                END

                IF @pData_Type = 'DATETIME'
                BEGIN
                    RETURN cast(@pExpressionDATEReplaced as DATETIME)
                END

            END
            ELSE
            BEGIN
                RETURN @pReturnValueIfErrorCast
            END
        END --FIN IF ISDATE(@pExpression) = 1
    END --FIN IF @pData_Type = 'DATE'

    --------------------------------------------------------------------------------
    --  NUMERIC 
    --------------------------------------------------------------------------------

    IF @pData_Type LIKE 'NUMERIC%'
    BEGIN

        IF ISNUMERIC(@pExpression) = 1
        BEGIN

            DECLARE @TotalDigitsOfType AS INT = SUBSTRING(@pData_Type,CHARINDEX('(',@pData_Type)+1,  CHARINDEX(',',@pData_Type) - CHARINDEX('(',@pData_Type) - 1)
                , @TotalDecimalsOfType AS INT = SUBSTRING(@pData_Type,CHARINDEX(',',@pData_Type)+1,  CHARINDEX(')',@pData_Type) - CHARINDEX(',',@pData_Type) - 1)
                , @TotalDigitsOfValue AS INT 
                , @TotalDecimalsOfValue AS INT 
                , @TotalWholeDigitsOfType AS INT 
                , @TotalWholeDigitsOfValue AS INT 

            SET @pExpression = REPLACE(@pExpression, ',','.')

            SET @TotalDigitsOfValue = LEN(REPLACE(@pExpression, '.',''))
            SET @TotalDecimalsOfValue = CASE Charindex('.', @pExpression)
                                        WHEN 0
                                            THEN 0
                                        ELSE Len(Cast(Cast(Reverse(CONVERT(VARCHAR(50), @pExpression, 128)) AS FLOAT) AS BIGINT))
                                        END 
            SET @TotalWholeDigitsOfType = @TotalDigitsOfType - @TotalDecimalsOfType
            SET @TotalWholeDigitsOfValue = @TotalDigitsOfValue - @TotalDecimalsOfValue

            -- The total digits can not be greater than the p part of NUMERIC (p, s)
            -- The total of decimals can not be greater than the part s of NUMERIC (p, s)
            -- The total digits of the whole part can not be greater than the subtraction between p and s
            IF (@TotalDigitsOfValue <= @TotalDigitsOfType) AND (@TotalDecimalsOfValue <= @TotalDecimalsOfType) AND (@TotalWholeDigitsOfValue <= @TotalWholeDigitsOfType)
            BEGIN
                DECLARE @pExpressionNUMERIC AS FLOAT
                SET @pExpressionNUMERIC = CAST (ROUND(@pExpression, @TotalDecimalsOfValue) AS FLOAT) 

                RETURN @pExpressionNUMERIC --Returns type FLOAT
            END 
            else
            BEGIN
                RETURN @pReturnValueIfErrorCast
            END-- FIN IF (@TotalDigitisOfValue <= @TotalDigits) AND (@TotalDecimalsOfValue <= @TotalDecimals) 

        END
        ELSE 
        BEGIN
            RETURN @pReturnValueIfErrorCast
        END --FIN IF ISNUMERIC(@pExpression) = 1
    END --IF @pData_Type LIKE 'NUMERIC%'

    --------------------------------------------------------------------------------
    --  BIT 
    --------------------------------------------------------------------------------

    IF @pData_Type LIKE 'BIT'
    BEGIN
        IF ISNUMERIC(@pExpression) = 1
        BEGIN
            RETURN CAST(@pExpression AS BIT) 
        END
        ELSE 
        BEGIN
            RETURN @pReturnValueIfErrorCast
        END --FIN IF ISNUMERIC(@pExpression) = 1
    END --IF @pData_Type LIKE 'BIT'


    --------------------------------------------------------------------------------
    --  FLOAT   
    --------------------------------------------------------------------------------

    IF @pData_Type LIKE 'FLOAT'
    BEGIN
        IF ISNUMERIC(REPLACE(REPLACE(@pExpression, CHAR(13), ''), CHAR(10), '')) = 1
        BEGIN

            RETURN CAST(@pExpression AS FLOAT) 
        END
        ELSE 
        BEGIN

            IF REPLACE(@pExpression, CHAR(13), '') = '' --Only white spaces are replaced, not new lines
            BEGIN
                RETURN 0
            END
            ELSE 
            BEGIN
                RETURN @pReturnValueIfErrorCast
            END --IF REPLACE(@pExpression, CHAR(13), '') = '' 

        END --FIN IF ISNUMERIC(@pExpression) = 1
    END --IF @pData_Type LIKE 'FLOAT'

    --------------------------------------------------------------------------------
    --  Any other unsupported data type will return NULL or the value assigned by the user to @pReturnValueIfErrorCast  
    --------------------------------------------------------------------------------

    RETURN @pReturnValueIfErrorCast



END

目前仅支持 INT、DATE、DATETIME、NUMERIC、BIT 和 FLOAT 数据类型。您可以在下面的链接中找到此代码的最新版本,并且我们可以相互帮助进行改进。SQL Server 2008 的 TRY_CAST 函数https://gist.github.com/jotapardo/800881eba8c5072eb8d99ce6eb74c8bb

3
您的函数依赖于ISNUMERIC函数,这使得它不可靠。执行SELECT dbo.TRY_CAST('+', 'INT', 0)将产生错误_Msg 8114, Level 16, State 5, Line 16 Error converting data type varchar to float._ - AperioOculus
2
@AperioOculus,非常感谢您的帮助。我已经更新了代码!https://gist.github.com/jotapardo/800881eba8c5072eb8d99ce6eb74c8bb - JotaPardo

5

在 Stack Overflow 和 Google 的帮助下,我终于找到了解决方法。

更新语句:

UPDATE PriceTerm 
SET PercentAddition = CONVERT(decimal(28,10), RTRIM(LTRIM(REPLACE(REPLACE(REPLACE(AdditionalDescription,'%',''), ',','.'), '&', '')))) 
WHERE AdditionalDescription LIKE '%[%]%' AND
dbo.isreallynumeric(RTRIM(LTRIM(REPLACE(REPLACE(REPLACE(AdditionalDescription,'%',''), ',','.'), '&', '')))) = 1 AND
PercentAddition = 0

首先,我搜索%字符,因为大多数情况下它被用作百分比值的标记。但是也有其他随机的用途。结果发现,在我这种情况下,ISNUMERIC不可靠。

真正有影响的是从这里调用的存储过程isreallynumeric。

所以:

CREATE FUNCTION dbo.isReallyNumeric  
(  
    @num VARCHAR(64)  
)  
RETURNS BIT  
BEGIN  
    IF LEFT(@num, 1) = '-'  
        SET @num = SUBSTRING(@num, 2, LEN(@num))  

    DECLARE @pos TINYINT  

    SET @pos = 1 + LEN(@num) - CHARINDEX('.', REVERSE(@num))  

    RETURN CASE  
    WHEN PATINDEX('%[^0-9.-]%', @num) = 0  
        AND @num NOT IN ('.', '-', '+', '^') 
        AND LEN(@num)>0  
        AND @num NOT LIKE '%-%' 
        AND  
        (  
            ((@pos = LEN(@num)+1)  
            OR @pos = CHARINDEX('.', @num))  
        )  
    THEN  
        1  
    ELSE  
    0  
    END  
END  
GO 

这个似乎是有效的,因为它不会给你一个错误的正结果(至少我还没有成功地破解它)。然而,在某些情况下,它可能会给你错误的负结果: 1(数字前有空格),1 (数字后有空格),+1。当然,根据您的要求,它们可能不是错误的负结果。我之所以称它们为错误的负结果,是因为1)您的函数在这些情况下的结果与ISNUMERIC的结果不一致,2)这些情况将无问题地转换为数值类型。 - Andriy M

5
你可以使用C#编写自己的自定义解析器,并使用SQLCLR,例如使用Decimal.Parse()。不要尝试使用ISNUMERIC,它经常返回错误结果(对于无法转换的字符串返回TRUE)。

9
ISNUMERIC 不是错误的,它只回答了一个实际上没有人想知道答案的问题(“我能将这个字符串转换为任何数字数据类型吗?”)。 - Damien_The_Unbeliever

2
我喜欢简单的解决方案,因此创建了以下类似的存储过程。
CREATE PROC TRY_CAST 
(   
    @valueToBeParsed VARCHAR(64), @parsedValue INT OUTPUT
)
AS
BEGIN
    BEGIN TRY
        SELECT @parsedValue = cast(@valueToBeParsed as int)
    END TRY
    BEGIN CATCH
    SET @parsedValue = null
    END CATCH
END
GO

并按以下方式使用它
DECLARE @val int
EXEC TRY_CAST '1w', @val out
select @val

0
CASE WHEN ISNUMERIC(yourColumn) = 1 
THEN CAST(yourColumn AS DECIMAL(28,10)) 
ELSE NULL --or Zero
END AS yourColumName

现在是2019年,我仍然在使用一个2008服务器,所以我有同样的问题。上面的解决方法对我奏效了。


-3

SQL SERVER 2008 R2有一个TRY_CAST函数,但如果您将数据库配置为与2005兼容,则会发现SQL SERVER无法识别它。

我使用了创建@jotapardo的函数,但将其重命名为TRY_CAST2,因为在这种情况下SS可以识别该函数。


1
你的回答如果能提供一个或两个例子会更好。 - O. Jones
2
TRY_CAST在SQL Server 2008 R2中不可用。 https://learn.microsoft.com/zh-cn/sql/t-sql/functions/try-cast-transact-sql - Riley Major

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接