24

我有一张桌子:

Account_Code | Desc
503100       | account xxx
503103       | account xxx
503104       | account xxx
503102A      | account xxx
503110B      | account xxx

Account_Code一个在哪里varchar

当我在下面创建查询时:

Select 
  cast(account_code as numeric(20,0)) as account_code,
  descr 
from account 
where isnumeric(account_code) = 1

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

但是当我尝试添加另一个选择时,嵌套到先前的 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

查询将返回错误

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

那里发生了什么?

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

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

4

6 回答 6

36

SQL Server 2012 and Later

Just use Try_Convert instead:

TRY_CONVERT takes the value passed to it and tries to convert it to the specified data_type. If the cast succeeds, TRY_CONVERT returns the value as the specified data_type; if an error occurs, null is returned. However if you request a conversion that is explicitly not permitted, then TRY_CONVERT fails with an error.

Read more about Try_Convert.

SQL Server 2008 and Earlier

The traditional way of handling this is by guarding every expression with a case statement so that no matter when it is evaluated, it will not create an error, even if it logically seems that the CASE statement should not be needed. Something like this:

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

However, I like using strategies such as this with SQL Server 2005 and up:

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

What this does is strategically switch the Account_Code values to NULL inside of the X table when they are not numeric. I initially used CROSS APPLY but as Mikael Eriksson so aptly pointed out, this resulted in the same error because the query parser ran into the exact same problem of optimizing away my attempt to force the expression order (predicate pushdown defeated it). By switching to OUTER APPLY it changed the actual meaning of the operation so that X.Account_Code could contain NULL values within the outer query, thus requiring proper evaluation order.

You may be interested to read Erland Sommarskog's Microsoft Connect request about this evaluation order issue. He in fact calls it a bug.

There are additional issues here but I can't address them now.

P.S. I had a brainstorm today. An alternate to the "traditional way" that I suggested is a SELECT expression with an outer reference, which also works in SQL Server 2000. (I've noticed that since learning CROSS/OUTER APPLY I've improved my query capability with older SQL Server versions, too--as I am getting more versatile with the "outer reference" capabilities of SELECT, ON, and WHERE clauses!)

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

It's a lot shorter than the CASE statement.

于 2013-01-04T08:47:13.067 回答
10

不能保证 SQL Server在运行子句中的过滤器之前CONVERT不会尝试执行to 。numeric(20,0) WHERE

而且,即使它确实如此,ISNUMERIC也是不够的,因为它识别£1d4作为数字,两者都不能转换为numeric(20,0).(*)

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

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


(*)ISNUMERIC回答了没有人(据我所知)想问的问题——“这个字符串可以转换为任何数字数据类型吗——我不在乎哪个?” - 显然,大多数人想问的是“这个字符串可以转换为 x 吗?” 其中x特定的目标数据类型。

于 2013-01-04T08:37:12.187 回答
9

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

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

TRY_CONVERT / TRY_CAST

如果转换成功,则返回一个转换为指定数据类型的值;否则,返回 null。

于 2013-08-08T13:02:38.723 回答
2

我认为问题不在于子查询,而在于外部查询的 WHERE 子句。当你使用

WHERE account_code between 503100 and 503105

SQL 服务器将尝试将 Account_code 字段中的每个值转换为整数,以在提供的条件下对其进行测试。显然,如果某些行中有非整数字符,它将无法这样做。

于 2013-11-12T04:36:42.670 回答
1

谢谢,试试这个

Select 
  STR(account_code) as account_code_Numeric,
  descr 
from account 
where  STR(account_code) = 1

我很乐意帮助你

于 2014-08-19T12:16:14.973 回答
0

如果在 where 子句中使用 varchar,则需要对 where 子句中使用的值使用撇号。所以我认为您的 where 子句值需要如下更正

WHERE account_code between '503100' and '503105'
于 2021-11-22T02:32:57.457 回答