SQL Server:将数据类型varchar转换为numeric时出错

use*_*840 21 sql-server varchar numeric

我有一张桌子:

Account_Code | Desc
503100       | account xxx
503103       | account xxx
503104       | account xxx
503102A      | account xxx
503110B      | account xxx
Run Code Online (Sandbox Code Playgroud)

哪里Account_Codevarchar.

当我在下面创建查询时:

Select 
  cast(account_code as numeric(20,0)) as account_code,
  descr 
from account 
where isnumeric(account_code) = 1
Run Code Online (Sandbox Code Playgroud)

它通过返回account_code列中具有有效数值的所有记录运行良好.

但是当我尝试添加另一个select时,嵌套到先前的sql:

select account_code,descr 
from 
(
  Select cast(account_code as numeric(20, 0)) as account_code,descr 
  from account 
  where isnumeric(account_code) = 1
) a 
WHERE account_code between 503100 and 503105
Run Code Online (Sandbox Code Playgroud)

查询将返回错误

将数据类型varchar转换为数字时出错.

那里发生了什么?

如果account_code有效,我已经转换为数字,但似乎查询仍在尝试处理无效的记录.

我需要BETWEEN在查询中使用 子句.

Eri*_*ikE 29

SQL Server 2012及更高版本

只需使用Try_Convert:

TRY_CONVERT获取传递给它的值并尝试将其转换为指定的data_type.如果转换成功,则TRY_CONVERT返回值作为指定的data_type; 如果发生错误,则返回null.但是,如果您请求明确不允许的转换,则TRY_CONVERT将失败并显示错误.

了解有关Try_Convert的更多信息.

SQL Server 2008和早期版本

处理此问题的传统方法是使用case语句保护每个表达式,这样无论何时进行求值,它都不会产生错误,即使它在逻辑上似乎不需要CASE语句.像这样的东西:

SELECT
   Account_Code =
      Convert(
         bigint, -- only gives up to 18 digits, so use decimal(20, 0) if you must
         CASE
         WHEN X.Account_Code LIKE '%[^0-9]%' THEN NULL
         ELSE X.Account_Code
         END
      ),
   A.Descr
FROM dbo.Account A
WHERE
   Convert(
      bigint,
      CASE
      WHEN X.Account_Code LIKE '%[^0-9]%' THEN NULL
      ELSE X.Account_Code
      END
   ) BETWEEN 503100 AND 503205
Run Code Online (Sandbox Code Playgroud)

但是,我喜欢在SQL Server 2005及更高版本中使用此类策略:

SELECT
   Account_Code = Convert(bigint, X.Account_Code),
   A.Descr
FROM
   dbo.Account A
   OUTER APPLY (
      SELECT A.Account_Code WHERE A.Account_Code NOT LIKE '%[^0-9]%'
   ) X
WHERE
   Convert(bigint, X.Account_Code) BETWEEN 503100 AND 503205
Run Code Online (Sandbox Code Playgroud)

这样做的目的是在Account_Code值不是数字时策略性地将值切换到表格NULL内部X.我最初使用CROSS APPLY但是正如Mikael Eriksson恰如其分地指出的那样,这导致了同样的错误,因为查询解析器遇到了完全相同的问题,即优化了我强制表达顺序的尝试(谓词下推击败了它).通过切换到OUTER APPLY它改变了操作的实际含义,因此X.Account_Code 可能包含NULL外部查询中的值,因此需要适当的评估顺序.

您可能有兴趣阅读有关此评估订单问题的Erland Sommarskog的Microsoft Connect请求.他实际上称之为一个错误.

这里还有其他问题,但我现在无法解决.

PS我今天有头脑风暴.我建议的"传统方式"的替代方案是SELECT带有外部引用的表达式,它也可以在SQL Server 2000中使用.(我注意到,自从学习以来,CROSS/OUTER APPLY我已经使用旧的SQL Server版本改进了我的查询功能 - -as我得到更灵活的与"外部参考"功能SELECT,ON以及WHERE条款!)

SELECT
   Account_Code =
      Convert(
         bigint,
         (SELECT A.AccountCode WHERE A.Account_Code NOT LIKE '%[^0-9]%')
      ),
   A.Descr
FROM dbo.Account A
WHERE
   Convert(
      bigint,
      (SELECT A.AccountCode WHERE A.Account_Code NOT LIKE '%[^0-9]%')
   ) BETWEEN 503100 AND 503205
Run Code Online (Sandbox Code Playgroud)

它比CASE声明短得多.


Dam*_*ver 9

无法保证SQL Server 在子句中运行过滤器之前不会尝试执行CONVERTto .numeric(20,0) WHERE

并且,即使它确实存在,ISNUMERIC也是不够的,因为它识别£并且1d4是数字,它们都不能被转换为numeric(20,0).(*)

将其拆分为两个单独的查询,第一个查询过滤结果并将它们放在临时表或表变量中,第二个执行转换.(子查询和CTE不足以阻止优化器在过滤器之前尝试转换)

对于您的过滤器,可能使用account_code not like '%[^0-9]%'而不是ISNUMERIC.


(*)ISNUMERIC回答了一个问题:没有人(据我所知)一直想问 - "这个字符串是否可以转换为任何数值数据类型 - 我不关心哪个?" - 显然,大多数人想问的是"这个字符串可以转换成x吗?" 其中x特定的目标数据类型.


mpr*_*ost 7

如果您运行的是SQL Server 2012,还可以使用新的TRY_PARSE()函数:

返回表达式的结果(转换为请求的数据类型),如果在SQL Server 2012中转换失败,则返回null.仅将TRY_PARSE用于从字符串转换为日期/时间和数字类型.