7

我有一个带有 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?

4

6 回答 6

8

SQL Server 不保证布尔运算符短路,请参阅关于 SQL Server 布尔运算符短路。因此,使用的所有解决方案ISNUMERIC(...) AND CAST(...)都存在根本缺陷(它们可能有效,但根据生成的计划,它们可能会在以后任意失败)。正如 Thomas 所建议的,更好的解决方案是使用 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(即非数字)。这会加快一些事情。

于 2010-11-04T19:02:54.767 回答
8

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

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

于 2013-07-22T09:52:19.117 回答
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
于 2010-11-04T18:49:57.200 回答
1
SELECT * 
    FROM MyTable 
    WHERE ISNUMERIC(MyRow) = 1
        AND CAST(MyRow as float) = 123456789012
于 2010-11-04T18:42:20.503 回答
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

于 2010-11-04T18:40:28.303 回答
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.]%'

于 2010-11-04T18:40:55.137 回答