防止在非限定索引视图上插入聚集索引操作符

coc*_*lla 8 sql-server

有谁知道解决方法?本质上,即使行不符合条件,存储过程也会对索引视图强制插入运算符。因此,存在强制转换错误。但是,对于临时用户,sql 正确地将视图排除在外。

考虑以下模式:

create table testdata (
    testid int identity(1,1) primary key
  , kind varchar(50)
  , data nvarchar(4000))
go
create view integer_testdata with schemabinding
as
select cast(a.data as int) data, a.kind, a.testid
  from dbo.testdata a
 where a.kind = 'integer'
go
create unique clustered index cl_intdata on integer_testdata(data)
go
create procedure insert_testdata
(
    @kind varchar(50)
  , @data nvarchar(4000)
)
as
begin
  insert into testdata (kind, data) values (@kind, @data)
end
go
Run Code Online (Sandbox Code Playgroud)

这些都有效:

insert into testdata (kind, data) values ('integer', '1234');
insert into testdata (kind, data) values ('integer', 12345);
insert into testdata (kind, data) values ('noninteger', 'noninteger');
exec insert_testdata @kind = 'integer', @data = '123456';
exec insert_testdata @kind = 'integer', @data = 1234567;
Run Code Online (Sandbox Code Playgroud)

这失败了:

exec insert_testdata @kind = 'noninteger', @data = 'noninteger';
Run Code Online (Sandbox Code Playgroud)

“估计执行计划”的比较:

insert into testdata (kind, data) values ('noninteger', 'noninteger')在此处输入图片说明

exec insert_testdata @kind = 'noninteger', @data = 'noninteger'在此处输入图片说明

Vla*_*nov 6

感谢您提供完整的脚本来重现问题。

我使用 SQL Server 2014 Express 对其进行了测试。

当我添加OPTION(RECOMPILE)它时:

ALTER procedure [dbo].[insert_testdata]
(
    @kind varchar(50)
  , @data nvarchar(4000)
)
as
begin
  insert into testdata (kind, data) 
  values (@kind, @data)
  OPTION(RECOMPILE);
end
Run Code Online (Sandbox Code Playgroud)

当我在 SSMS 中运行它时:

exec insert_testdata @kind = 'noninteger', @data = 'noninteger';
Run Code Online (Sandbox Code Playgroud)

我收到这条消息:

(1 row(s) affected)
Run Code Online (Sandbox Code Playgroud)

并将一行添加到表中。

您使用的是什么版本的 SQL Server?我依稀记得在 2008 年之前的版本中,这OPTION(RECOMPILE)表现得有点不同。


我正在使用现有的数据结构,需要快速查找存储在 nvarchar(4000) 子集中的唯一整数值​​,另一列上的过滤器定义了该行子集。

在这种情况下,最好使用过滤索引而不是索引视图:

CREATE UNIQUE NONCLUSTERED INDEX [IX_DataFiltered] ON [dbo].[testdata]
(
    [data] ASC
)
WHERE ([kind]='integer')
Run Code Online (Sandbox Code Playgroud)

WHERE查询的过滤器与WHERE索引的子句完全匹配时,优化器应使用此索引。

是的,这里的索引位于nvarchar列上,这可能不是最好的事情,特别是如果您将此表与int另一个表的列连接起来,或者尝试使用int值过滤此列中的值。


想到的另一个变体是持久化计算列,它转换nvarcharint. 本质上它与您的视图非常相似,但是nvarchar转换成的持久值int存储在同一个表中,而不是存储在单独的对象中。

CREATE TABLE [dbo].[testdata](
    [testid] [int] IDENTITY(1,1) NOT NULL,
    [kind] [varchar](50) NULL,
    [data] [nvarchar](4000) NULL,
    [int_data]  AS (case when [kind]='integer' then CONVERT([int],[data]) end) PERSISTED,
PRIMARY KEY CLUSTERED 
(
    [testid] ASC
))


CREATE UNIQUE NONCLUSTERED INDEX [IX_int_data_filtered] ON [dbo].[testdata]
(
    [int_data] ASC
)
WHERE ([kind]='integer')
Run Code Online (Sandbox Code Playgroud)

通过此设置,我尝试使用您的原始存储过程插入行,即使没有OPTION(RECOMPILE).


实际上,似乎上述持久列有效的主要原因是我使用CASE. 如果我添加CASE到您的视图定义中,则存储过程无需OPTION(RECOMPILE).

create view integer_testdata2 with schemabinding
as
select 
    case when a.kind='integer' then CONVERT(int, a.data) end as data
    , a.kind, a.testid
from dbo.testdata a
where a.kind = 'integer'
go
Run Code Online (Sandbox Code Playgroud)