我在SQL server中使用ISNUMERIC
函数时遇到问题。
我有一个表,其中包含一个nvarchar
类型的列。在这一列中,我有这样的值123
、241
。。。有时字符串值如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
我的期望是,Common表表达式过滤所有行,哪一列是数字而不是字符串?这是正确的吗?
为什么我会收到此错误?
这实际上没有bug。
CTE是一个临时视图(或类似于临时视图)。所以,像查询视图一样思考我们的查询。最有可能的是,SQL将首先尝试遍历所有行,并从两个标量(isnumeric和cast)中获得结果,然后继续进行筛选。
话虽如此,它在尝试过滤演员表上的数据之前就会失败。
如果你想让它发挥作用,只需在临时表或表变量中过滤之前的数据。
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
您可以通过添加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
它与如何解析和执行查询有关。如果不能按照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
主要问题是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数据库真正发光发热:)
我认为下面的查询更有效:
SELECT *
FROM MyTable
WHERE ISNUMERIC([Column1]) = 1 AND CAST(cte.Column1 AS INT) > 8000