SQL2000 安全地将 VARCHAR(256) 转换为 INT

发布于 2024-08-18 06:14:38 字数 2449 浏览 6 评论 0原文

我在 SQL2000 上将 varchar 安全地转换为 int 时遇到一些问题。

我的问题的第 1 部分是 IsNumeric 返回如果您仅查找整数,则误报。我知道为什么 IsNumeric 会这样做(浮点数、金钱等也是数字),所以我在谷歌上寻找 IsInteger 函数。

我发现以下用户定义函数(UDF):

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

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

这似乎在检查整数方面做得很好:

declare @num varchar(256);
declare @num2 varchar(256);
set @num = '22312311';
set @num2 = '22312311.0';
SELECT  @num AS [character], 
 dbo.IsInteger(@num) AS [isInteger], 
 CASE dbo.IsInteger(@num)WHEN 1 THEN convert(int, @num) ELSE NULL END AS [integer]
UNION
SELECT  @num2 AS [character], 
 dbo.IsInteger(@num2) AS [isInteger], 
 CASE dbo.IsInteger(@num2)WHEN 1 THEN convert(int, @num2) ELSE NULL END AS [integer];

但是它不会验证整数是否在范围内(-2^31 <=> 2^31 - 1)

declare @num varchar(256);
set @num = '2147483648';
SELECT  @num AS [character], 
 dbo.IsInteger(@num) AS [isInteger], 
 CASE dbo.IsInteger(@num)WHEN 1 THEN convert(int, @num) ELSE NULL END AS [integer];

抛出

服务器:Msg 248,Level 16,State 1,Line 3
nvarchar 值“2147483648”的转换溢出了 int 列。超出最大整数值。

SQL2000 没有 TRY/ CATCH (答案假定 ISNUMERIC() 返回不误报)并且转换错误会导致整个批次失败,即使在 UDF 内,根据 此网站

当UDF发生错误时, 函数的执行被中止 立即,查询也是如此,并且 除非错误是导致中止的错误 批处理,继续执行 下一条语句 – 但 @@error 是 0!

即使他们没有这样做,仍然会掩盖@@error。我也无法转换为 bigint,因为它仍然可能崩溃(尽管不那么频繁),并且此查询是 UNION 的一部分,该 UNION 输出到 XML,并由 VB6 使用 XSLT 进行进一步验证和转换COM DLL 并显示在 2001 年编码的网站上,所以我真的(不真的)不想更改查询输出!

因此,这让我陷入了这个看似简单的任务:

如果 varchar 可转换为 int 则转换为 int 否则给我 NULL

任何指针/解决方案都会非常感激,但请注意,我不能,在没有根据情况,更改源列的数据类型,也不更改输入数据时的验证。

I'm having some problem safely casting a varchar to int on SQL2000.

Part 1 of my problem was that IsNumeric returns false positives if your looking for integers only. I'm aware though why IsNumeric does this though (floats, money etcetera are numeric too) so i looked for an IsInteger function on google.

I found the following User Defined Function (UDF):

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

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

this seems to do a good job checking for integers:

declare @num varchar(256);
declare @num2 varchar(256);
set @num = '22312311';
set @num2 = '22312311.0';
SELECT  @num AS [character], 
 dbo.IsInteger(@num) AS [isInteger], 
 CASE dbo.IsInteger(@num)WHEN 1 THEN convert(int, @num) ELSE NULL END AS [integer]
UNION
SELECT  @num2 AS [character], 
 dbo.IsInteger(@num2) AS [isInteger], 
 CASE dbo.IsInteger(@num2)WHEN 1 THEN convert(int, @num2) ELSE NULL END AS [integer];

However it won't validate if the integer is within range (-2^31 <=> 2^31 - 1)

declare @num varchar(256);
set @num = '2147483648';
SELECT  @num AS [character], 
 dbo.IsInteger(@num) AS [isInteger], 
 CASE dbo.IsInteger(@num)WHEN 1 THEN convert(int, @num) ELSE NULL END AS [integer];

Which throws

Server: Msg 248, Level 16, State 1, Line 3
The conversion of the nvarchar value '2147483648' overflowed an int column. Maximum integer value exceeded.

SQL2000 doesn't have TRY/CATCH (answer presumes ISNUMERIC() returns no false positives) and casting errors cause the entire batch to fail even within UDF's according to this website:

When an error occurs in a UDF,
execution of the function is aborted
immediately and so is the query, and
unless the error is one that aborts
the batch, execution continues on the
next statement – but @@error is 0!

and even if they didn't would still obscure @@error. I also can't cast to bigint since it might still crash (albeit not as often) and this query is part of a UNION which is output to XML which is further validated and transformed with XSLT by a VB6 COM DLL and displayed on a website coded back in 2001 so I really (no really) do not want to change the query output!.

So this leaves me stuck on this seemingly easy task:

if varchar is castable to int cast to int otherwise give me NULL

Any pointers / solutions would be much apreciated but please note that I can't, under no circumstance, change the source column's datatype nor change the validation when data is entered.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

街道布景 2024-08-25 06:14:38

编辑:

SQL Server 中的数字不能超过 decimal(38,0)< /a> (+/- 10^38 -1) 因此无法捕获或转换它们。这意味着长度可以为 37 个字符,并且 CAST 为十进制(38,0)

SELECT
    CASE
        WHEN CAST(MyColumn AS decimal(38,0) BETWEEN -2147483648 AND 2147483647 THEN  CAST(MyColumn AS int)
        ELSE NULL
    END
FROM
    MyTable
WHERE
    ISNUMERIC(MyColumn + '.0e0') = 1 AND LEN(MyColumn) <= 37

尊重 本文介绍了 .0e0 技巧

编辑OP
这个问题引导我了解以下更新的 IsInteger 函数。

CREATE FUNCTION dbo.IsInteger
(  
    @num VARCHAR(256)  
)  
RETURNS BIT
BEGIN    
    RETURN CASE 
            WHEN ISNUMERIC(@num + '.0e0') = 1  AND convert(decimal(38,0), @num) BETWEEN -2147483648 AND 2147483647 THEN  1
            ELSE 0
    END
END

Edit:

You can not have numbers over decimal(38,0) in SQL Server (+/- 10^38 -1) so can not trap them or convert them. Which means 37 characters may length and a CAST to decimal(38,0)

SELECT
    CASE
        WHEN CAST(MyColumn AS decimal(38,0) BETWEEN -2147483648 AND 2147483647 THEN  CAST(MyColumn AS int)
        ELSE NULL
    END
FROM
    MyTable
WHERE
    ISNUMERIC(MyColumn + '.0e0') = 1 AND LEN(MyColumn) <= 37

Respect to this article for the .0e0 trick

EDIT OP
This question lead me to the folowing updated IsInteger function.

CREATE FUNCTION dbo.IsInteger
(  
    @num VARCHAR(256)  
)  
RETURNS BIT
BEGIN    
    RETURN CASE 
            WHEN ISNUMERIC(@num + '.0e0') = 1  AND convert(decimal(38,0), @num) BETWEEN -2147483648 AND 2147483647 THEN  1
            ELSE 0
    END
END
傲鸠 2024-08-25 06:14:38

您可以在该函数中添加更多检查:

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

    DECLARE @IsInt BIT

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

    IF @IsInt = 1
        BEGIN

            IF LEN(@num) <= 11
                BEGIN
                    DECLARE @test bigint
                    SELECT @test = convert(bigint, @num)
                    IF @test <= 2147483647 AND @test >= -2147483648
                        BEGIN
                            set @IsInt = 1
                        END
                    ELSE
                        BEGIN
                            set @IsInt = 0
                        END
                END
            ELSE
                BEGIN
                    set @IsInt = 0
                END
        END


    RETURN @IsInt

END 

我没有机会测试,但我认为它应该有效 - 我已将其尽可能详细地保留

You could just add a couple more checks into the function:

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

    DECLARE @IsInt BIT

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

    IF @IsInt = 1
        BEGIN

            IF LEN(@num) <= 11
                BEGIN
                    DECLARE @test bigint
                    SELECT @test = convert(bigint, @num)
                    IF @test <= 2147483647 AND @test >= -2147483648
                        BEGIN
                            set @IsInt = 1
                        END
                    ELSE
                        BEGIN
                            set @IsInt = 0
                        END
                END
            ELSE
                BEGIN
                    set @IsInt = 0
                END
        END


    RETURN @IsInt

END 

I've not had a chance to test but I think it should work - I've left it as verbose as possible

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文