empêcher l'opérateur d'insertion d'index en cluster sur une vue indexée non qualifiée

8

Est-ce que quelqu'un connaît une solution de contournement pour cela? Essentiellement, la procédure stockée force un opérateur d'insertion contre la vue indexée, même si les lignes ne sont pas qualifiées. Il en résulte une erreur de transtypage. Cependant, pour les ad hocs, sql élimine correctement la vue de toute considération.

Considérez le schéma suivant:

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

Tout cela fonctionne:

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;

Cela échoue:

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

Une comparaison des "plans d'exécution estimés":

insert into testdata (kind, data) values ('noninteger', 'noninteger'): entrez la description de l'image ici

exec insert_testdata @kind = 'noninteger', @data = 'noninteger': entrez la description de l'image ici

cocogorilla
la source
Des différences notables entre le plan de proc stocké ad hoc et mis en cache par hasard?
Ali Razeghi du
oui, lorsque vous exécutez ad hoc, vous n'obtenez aucun opérateur contre la vue indexée ... Je pense que sql est assez intelligent pour voir qu'il y a un filtre sur la vue et l'éliminer de la considération (cette élimination ne se produit pas dans le proc)
cocogorilla
4
Vous n'êtes pas en mesure de tester, mais l'ajout d' option (recompile)aides?
Martin Smith
2
Par curiosité, quel problème essayez-vous de résoudre. Cela sent comme un problème XY .
Max Vernon
1
@MaxVernon Je travaille avec une structure de données existante et j'ai besoin d'une recherche rapide sur des valeurs entières uniques stockées dans un sous-ensemble de nvarchar (4000), un filtre sur une autre colonne définit ce sous-ensemble de lignes.
cocogorilla

Réponses:

6

Merci d'avoir fourni un script complet pour recréer le problème.

Je l'ai testé avec SQL Server 2014 Express.

Quand j'ajoute OPTION(RECOMPILE)ça marche:

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

Lorsque je lance cela dans SSMS:

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

Je reçois ce message:

(1 row(s) affected)

et une ligne est ajoutée au tableau.

Quelle version de SQL Server utilisez-vous? Je me souviens vaguement que dans les versions antérieures à 2008, cela OPTION(RECOMPILE)se comportait un peu différemment.


Je travaille avec une structure de données existante et j'ai besoin d'une recherche rapide sur des valeurs entières uniques stockées dans un sous-ensemble de nvarchar (4000), un filtre sur une autre colonne définit ce sous-ensemble de lignes.

Dans ce cas, il peut être préférable d'utiliser un index filtré au lieu d'une vue indexée:

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

L'optimiseur doit utiliser cet index lorsque le WHEREfiltre de la requête correspond exactement à la WHEREclause de l'index.

Oui, ici l'index est sur une nvarcharcolonne, ce qui n'est peut-être pas la meilleure chose, surtout si vous joignez cette table à une intcolonne d'une autre table ou essayez de filtrer les valeurs de cette colonne à l'aide de intvaleurs.


Une autre variante qui vient à l'esprit est la colonne calculée persistante qui se convertit nvarcharen int. En substance, il est très similaire à votre vue, mais les nvarcharvaleurs persistantes qui sont converties en intsont stockées avec la même table, pas dans un objet séparé.

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')

Avec cette configuration, j'ai essayé d'utiliser votre procédure stockée d'origine pour insérer des lignes et cela a fonctionné même sans OPTION(RECOMPILE).


En fait, il semble que la principale raison pour laquelle la colonne persistante ci-dessus fonctionne est que j'utilise CASE. Si j'ajoute CASEà la définition de votre vue, la procédure stockée fonctionne sans 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
Vladimir Baranov
la source
Je ne suis pas sûr que l'index filtré fonctionnera bien car la largeur de colonne est de 4000 (bien au-dessus de la limite de 900). Je n'avais pas pensé à utiliser une option d'index de requête recompiler ... J'appliquais recompile à la procédure entière. Votre suggestion fonctionne pour tous mes cas de test! Je vous remercie.
cocogorilla du
1
Oui, l'index filtré sur la colonne d'origine peut ne pas être très utile. J'ai ajouté une autre variante avec une colonne calculée persistante.
Vladimir Baranov
J'adore l'option de colonne calculée persistante ... qui me semble être la bonne solution
cocogorilla