【问题标题】:bug in ISNUMERIC function?ISNUMERIC 函数中的错误?
【发布时间】:2016-04-11 07:38:13
【问题描述】:

我对 SQL 服务器中的 ISNUMERIC 函数有疑问。

我有一个表,其中包含一个 nvarchar 类型的列。在本专栏中,我有这样的值 123241、...,有时还有像 LK 这样的字符串值 我的查询:

WITH CTE AS (
    SELECT *
    FROM MyTable
    WHERE ISNUMERIC([Column1]) = 1
)
SELECT *
FROM CTE
WHERE CAST(cte.Column1 AS INT) > 8000

但此查询返回此错误消息:

error converting data type nvarchar 'LK' to int

我的期望是,公用表表达式过滤所有行,其中 Column1 是数字而不是字符串?对吗?

为什么我会收到此错误?

【问题讨论】:

  • 这与 SQL Server 解析和执行 SQL 的顺序有关。如果您想确定,您将不得不使用临时表而不是 CTE。或者根据您的服务器版本,如果您可以访问它们,请尝试解析函数。但是请注意,ISNUMERIC 可以为您无法解析为 INT 的内容返回 1,因此您的代码在其他地方也会失败
  • SQL server 中是否有替代功能可以解决我的问题?
  • 您使用的是哪个版本的 SQL Server?
  • Microsoft SQL Server 2014 - 12.0.4213.0 (X64) 2015 年 6 月 9 日 12:06:16 版权所有 (c) Microsoft Corporation Enterprise Edition:Windows NT 6.3 上基于内核的许可(64 位) (Build 9600: ) (Hypervisor)

标签: sql sql-server sql-server-2014


【解决方案1】:

这实际上没有错误。

CTE 是一个临时视图(或类似于临时视图)。所以像查询视图一样思考我们的查询。很可能 SQL 会首先尝试遍历所有行并从两个标量(isnumeric 和 cast)中获取结果,然后继续进行过滤。

话虽如此,它会在尝试过滤演员表上的数据之前失败。

如果你想让它工作,只需在临时表或表变量中过滤之前的数据。

【讨论】:

  • 谢谢你的回答,但是我的subselect也有同样的问题也是因为你解释的原因?
  • @Kaja 是的,子选择也有同样的问题。子选择不要在任何现代数据库引擎中创建临时表。
【解决方案2】:
IF OBJECT_ID('dbo.tbl', 'U') IS NOT NULL
    DROP TABLE dbo.tbl
GO

CREATE TABLE dbo.tbl (val NVARCHAR(1000))
INSERT INTO dbo.tbl
VALUES ('123'), ('234'), ('LK'), ('8001')

ALTER TABLE dbo.tbl
    ADD val2 AS CASE WHEN ISNUMERIC(val) = 1 THEN CAST(val AS INT) END
GO

SELECT *
FROM dbo.tbl
WHERE val2 > 8000

【讨论】:

    【解决方案3】:

    您可以通过添加TOP X来强制实现CTE表,以避免SQL Server优化where条件使其“短路”行为。

    WITH CTE AS (
        SELECT TOP 10000000000000 
            *
        FROM (VALUES('1'), ('LK')) t(Column1)
        WHERE ISNUMERIC([Column1]) = 1
    )
    SELECT *
    FROM CTE
    WHERE CAST(cte.Column1 AS INT) > 8000
    

    【讨论】:

      【解决方案4】:

      它与查询的解析和执行方式有关。如果无法按照 Devart 的建议添加列,则可以使用临时表。

      CREATE TABLE MyTable
      (
      Column1 nvarchar(50)
      )
      
      INSERT INTO MyTable
          VALUES ('123'),
          ('241'),
          ('LK'),
          ('786'),
          ('54321'),
          ('7999'),
          ('8001')
      
      SELECT
          * INTO #TEMP
      FROM MyTable
      WHERE ISNUMERIC([Column1]) = 1
      
      SELECT
          *
      FROM #TEMP
      WHERE CAST(Column1 AS int) > 8000
      
      DROP TABLE #TEMP
      

      【讨论】:

        【解决方案5】:

        主要问题是 SQL 是一种声明性语言,对操作的顺序很少。这是允许引擎优化查询所必需的。

        短路起作用的一种情况是在case 语句中。所以你可以尝试这样的事情:

        drop table #tbl;
        
        create table #tbl (txt varchar(10))
        insert into  #tbl
         values ('123'), ('234'), ('LK'), ('9001')
        
        select * from #tbl 
        where 
         (
          case 
           when isnumeric(txt) = 1 then 
            case when cast(txt as int) > 8000 then 1 end 
          end
         ) = 1
        

        不过,我无法告诉您性能将如何发挥作用。一般来说,你应该尽可能地使用规范化数据——它让 SQL 数据库真正发挥作用:)

        【讨论】:

          【解决方案6】:

          我认为下面的查询效率更高:

          SELECT *
          FROM MyTable
          WHERE ISNUMERIC([Column1]) = 1 AND  CAST(cte.Column1 AS INT) > 8000
          

          【讨论】:

          • 此查询将返回与 OP 中发布的完全相同的错误
          猜你喜欢
          • 2014-05-12
          • 1970-01-01
          • 2013-02-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-06-26
          相关资源
          最近更新 更多