запретить оператор вставки кластеризованного индекса в неквалифицированное индексированное представление

8

Кто-нибудь знает обходной путь для этого? По сути, хранимая процедура заставляет оператор вставки использовать индексированное представление, даже если строки не соответствуют требованиям. В результате возникает ошибка приведения. Однако для ad hocs 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

Это все работает:

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;

Это не удается:

exec insert_testdata @kind = 'noninteger', @data = 'noninteger';

Сравнение «сметных планов выполнения»:

insert into testdata (kind, data) values ('noninteger', 'noninteger'): введите описание изображения здесь

exec insert_testdata @kind = 'noninteger', @data = 'noninteger': введите описание изображения здесь

cocogorilla
источник
Какие-нибудь заметные различия между планом ad hoc и кэшированным хранимым протоколом на случай?
Али Разеги
да, когда вы выполняете ad hoc, вы не получаете никаких операторов против индексированного представления ... Я думаю, что sql достаточно умен, чтобы видеть, что в представлении есть фильтр, и исключать его из рассмотрения (это исключение не происходит в proc)
кокогорилла
4
Не в состоянии проверить, но помогает ли добавление option (recompile)?
Мартин Смит
2
Просто из любопытства, какую проблему вы пытаетесь решить. Это пахнет проблемой XY .
Макс Вернон
1
@MaxVernon Я работаю с существующей структурой данных и мне нужен быстрый поиск уникальных целочисленных значений, хранящихся в подмножестве nvarchar (4000), фильтр в другом столбце определяет это подмножество строк.
Cocogorilla

Ответы:

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

Когда я запускаю это в SSMS:

exec insert_testdata @kind = 'noninteger', @data = 'noninteger';

Я получаю это сообщение:

(1 row(s) affected)

и строка добавляется в таблицу.

Какую версию SQL Server вы используете? Я смутно помню, что в версиях до 2008 года он OPTION(RECOMPILE)вел себя немного иначе.


Я работаю с существующей структурой данных и мне нужен быстрый поиск уникальных целочисленных значений, хранящихся в подмножестве nvarchar (4000), фильтр в другом столбце определяет это подмножество строк.

В этом случае может быть лучше использовать отфильтрованный индекс вместо индексированного представления:

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

Оптимизатор должен использовать этот индекс, когда WHEREфильтр запроса точно соответствует WHEREпредложению индекса.

Да, здесь индекс находится по nvarcharстолбцу, что может быть не лучшим решением, особенно если вы объединяете эту таблицу со intстолбцом другой таблицы или пытаетесь отфильтровать значения в этом столбце, используя intзначения.


Еще один вариант, который приходит на ум, - это постоянный вычисляемый столбец, который преобразуется nvarcharв int. По сути это очень похоже на ваше представление, но сохраненные 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')

С этой настройкой я попытался использовать вашу оригинальную хранимую процедуру для вставки строк, и она работала даже без 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
Владимир Баранов
источник
Я не уверен, что отфильтрованный индекс будет работать хорошо, потому что ширина столбца равна 4000 (что значительно превышает предел 900). Я не думал об использовании опции подсказки запроса на перекомпиляцию ... Я применял перекомпиляцию ко всей процедуре. Ваше предложение работает для всех моих тестовых случаев! Спасибо.
cocogorilla
1
Да, отфильтрованный индекс в исходном столбце может быть не очень полезным. Я добавил другой вариант с сохраненным вычисляемым столбцом.
Владимир Баранов
Мне нравится опция сохраняемых вычисляемых столбцов ... которая мне кажется правильным решением
cocogorilla