Iro*_*fin 8 sql-server view type-conversion
我已经多次遇到过这种情况,我相信这是有充分理由的 - 但我该如何避免呢?
我确定这与周围的怪癖有关isnumeric
。在英语中,我有一个过滤isnumeric(somefield) = 1
. 然后我尝试int
在我的where
子句中使用 an 来查询它,因为表中的其他一些字段有字符值,所以整个事情都失败了。
我创建了一个显示错误的SQL 小提琴。
我尝试cast/convert
在视图的 select 中执行 a ,但查询引擎似乎忽略了它。
那么 - 为什么会发生这种情况,有没有一种干净的方法来处理它?
由于我在插入物上缺少引号,原始小提琴有一个非常相似的错误。这不是我试图强调的问题。我通过为所有插入的值添加引号来修复视图。
该视图没有被使用,因为它正在被查询优化器“优化掉”。您可以通过查看查询计划来看到这一点:
您可以创建一个索引物化视图,然后在目标查询上使用NOEXPAND
表提示来防止这种优化。
一个例子:
USE tempdb;
IF OBJECT_ID(N'dbo.TestView', N'V') IS NOT NULL
BEGIN
DROP VIEW dbo.TestView;
DROP TABLE dbo.Test;
END
CREATE TABLE dbo.Test
(
TableKey int
, TestField nvarchar(24)
);
GO
Run Code Online (Sandbox Code Playgroud)
创建一个视图,使用 SCHEMABINDING:
CREATE VIEW dbo.TestView
WITH SCHEMABINDING
AS
SELECT
t.TableKey
, t.TestField
FROM
dbo.Test t
WHERE ISNUMERIC(t.TestField) = 1;
GO
Run Code Online (Sandbox Code Playgroud)
在视图上创建索引:
CREATE UNIQUE CLUSTERED INDEX PK_TestView
ON dbo.TestView (TableKey);
GO
Run Code Online (Sandbox Code Playgroud)
插入测试数据:
INSERT INTO dbo.Test
select 0, '0'
union select 1, 'Rejected'
union select 2, 'Unlinked'
union select 0, '0'
union select 3, '1'
union select 162,'1000'
union select 16, '10000'
union select 17, '10010'
union select 18, '10011'
union select 19, '10012'
union select 20, '10031'
union select 21, '10041'
Run Code Online (Sandbox Code Playgroud)
查询视图:
SELECT *
FROM dbo.TestView WITH (NOEXPAND)
WHERE Testfield = 1000
Run Code Online (Sandbox Code Playgroud)
结果:
??????????????????????????? ? 表键?测试场? ??????????????????????????? ? 162?1000 ? ???????????????????????????
查询计划:
请注意上面查询计划中 SELECT 节点中的三角形感叹号 - 这是一个警告:
表达式中的类型转换 (CONVERT_IMPLICIT(int,[tempdb].[dbo].[TestView].[TestField],0)) 可能会影响查询计划选择中的“CardinalityEstimate”,表达式中的类型转换 (CONVERT_IMPLICIT(int,[tempdb]) .[dbo].[TestView].[TestField],0)=CONVERT_IMPLICIT(int,[@1],0)) 可能会影响查询计划选择中的“SeekPlan”
如果我们像这样修改视图,可以消除类型转换警告:
CREATE VIEW dbo.TestView
WITH SCHEMABINDING
AS
SELECT
t.TableKey
, TestField = TRY_CONVERT(int, t.TestField)
FROM
dbo.Test t
WHERE ISNUMERIC(t.TestField) = 1;
Run Code Online (Sandbox Code Playgroud)
现在,视图将返回TestField
类型为整数的列,并且由于我们NOEXPAND
与持久数据结合使用,因此消除了隐式类型转换:
正如其他答案所解释的那样,问题在于INSERT
语句,并且 - 当它被修复时 - 视图被优化掉了。
结果是查询where Testfield = 1000
将尝试为所有行以及列的所有值转换TestField
为数字(由于数据类型优先规则)。
执行顺序很难强制,因为优化器可以自由地重写查询,并且不保证标量表达式的计算时间、顺序或数量。
解决此问题的一种方法是使用CASE
表达式。仍然不能绝对保证避免此类问题,但它几乎总是有效。在sqlfiddle.com 中测试:
CREATE VIEW dbo.TestView
AS
SELECT
TableKey,
TestField = CASE WHEN IsNumeric(TestField) = 1 THEN TestField END
FROM
dbo.Test
WHERE
IsNumeric(TestField) = 1 ;
Run Code Online (Sandbox Code Playgroud)
另请参阅 Aaron Bertrand 的这个答案以获得更详细的解释:CTE Error (nvarchar to numeric)以及TRY_CONVERT()
如果您使用的版本足够新,则建议使用更强大的版本。