SQL无效转换返回null而不是抛出错误

时间:2010-11-04 18:35:35

标签: tsql invalid-characters

我有一个带varchar列的表,我想找到与某个数字匹配的值。因此,假设该列包含以下条目(现实生活中除了数百万行):

123456789012
2345678
3456
23 45
713?2
00123456789012

所以我决定我想要所有数字上的行123456789012写一个看起来像这样的语句:

SELECT * FROM MyTable WHERE CAST(MyColumn as bigint) = 123456789012

它应该返回第一行和最后一行,但整个查询会爆炸,因为它无法将“23 45”和“713?2”转换为bigint。

是否有另一种方法可以进行转换,为无法转换的值返回NULL?

6 个答案:

答案 0 :(得分:8)

如果您使用的是SQL Server 2012,则可以使用以下两种新方法:

两种方法都是等价的。如果转换成功,它们返回一个值转换为指定的数据类型;否则,返回null。唯一的区别是CONVERT是SQL Server特定的,CAST是ANSI。使用CAST将使您的代码更具可移植性(虽然不确定是否有任何其他数据库提供程序实现TRY_CAST)

答案 1 :(得分:7)

SQL Server不保证布尔运算符短路,请参阅On SQL Server boolean operator short-circuit。所以使用ISNUMERIC(...) AND CAST(...)的所有解决方案都存在根本缺陷(它们可能会起作用,但是嘿可以随意地依赖于生成的计划而失败)。正如托马斯建议的那样,更好的解决方案是使用CASE:CASE ISNUMERIC(...) WHEN 1 THEN CAST(...) ELSE NULL END。但是,正如gbn所指出的那样,ISNUMERIC在识别“数字”意味着什么以及许多人希望它返回0的情况下是非常挑剔的,它返回1.因此将CASE与LIKE混合:

CASE WHEN MyRow NOT LIKE '%[^0-9]%' THEN CAST(MyRow as bigint) ELSE NULL END

但真正的问题是,如果你有数百万行而你必须像这样搜索它们,你总是会端对端扫描,因为表达式不具备SARG能力(无论我们如何重写它)。这里真正的问题是数据纯度,应该在适当的级别解决数据填充的问题。另一件需要考虑的事情是,是否可以使用此表达式创建一个持久的计算列,并在其上创建一个消除NULL的过滤索引(即非数字)。这会加速一些事情。

答案 2 :(得分:4)

ISNUMERIC将接受空字符串和值,如1.23或5E-04,因此可能不可靠。

而且您不知道将对哪些订单进行评估,因此它仍然可能失败(SQL是声明性的,而不是程序性的,因此WHERE子句可能不会从左到右进行评估)

所以:

  • 您希望接受仅包含字符0-9
  • 的值
  • 您需要实现“数字”过滤器,以便在CAST之前应用

类似的东西:

SELECT 
*
FROM
   (
   SELECT TOP 2000000000 *
   FROM MyTable
   WHERE MyColumn NOT LIKE '%[^0-9]%'  --double negative rejects anything except 0-9
   ORDER BY MyColumn 
   ) foo
WHERE 
    CAST(MyColumn as bigint) = 123456789012 --applied after number check

编辑:失败的快速示例。

CREATE TABLE #foo (bigintstring varchar(100))
INSERT #foo (bigintstring )VALUES ('1.23')
INSERT #foo (bigintstring )VALUES ('1 23')
INSERT #foo (bigintstring )VALUES ('123')

SELECT * FROM #foo
WHERE
   ISNUMERIC(bigintstring) = 1 
   AND 
   CAST(bigintstring AS bigint) = 123

答案 3 :(得分:1)

SELECT * 
    FROM MyTable 
    WHERE ISNUMERIC(MyRow) = 1
        AND CAST(MyRow as float) = 123456789012

答案 4 :(得分:0)

ISNUMERIC()函数应该可以满足您的需求。

SELECT * FROM MyTable
WHERE ISNUMERIC(MyRow) = 1
AND CAST(MyRow as bigint) = 123456789012

并添加像托马斯建议的案例陈述:

SELECT * FROM MyTable
WHERE CASE(ISNUMERIC(MyRow)
        WHEN 1 THEN CAST(MyRow as bigint)
        ELSE NULL
      END = 123456789012

http://msdn.microsoft.com/en-us/library/ms186272.aspx

答案 5 :(得分:0)

SELECT *
FROM MyTable
WHERE (ISNUMERIC(MyColumn) = 1) AND (CAST(MyColumn as bigint) = 123456789012)

此外,您可以使用CASE语句来获取空值。

SELECT 
    CASE
        WHEN (ISNUMERIC(MyColumn) = 1) THEN CAST(MyColumn as bigint)
        ELSE NULL
    END AS 'MyColumnAsBigInt'
FROM tableName

如果您需要额外的过滤,对于无法转换为bigint的数字,您可以使用以下代替ISNUMERIC:

PATINDEX('%[^ 0-9]%',MyColumn))= 0

如果您需要十进制值而不是整数,请转换为浮点值并将正则表达式更改为'%[^ 0-9。]%'