evitar el operador de inserción de índice agrupado en una vista indizada no calificada

8

¿Sabe alguien una solución para esto? Esencialmente, el procedimiento almacenado fuerza a un operador de inserción contra la vista indexada, aunque las filas no califican. Como resultado, hay un error de lanzamiento. Sin embargo, para ad hocs, sql elimina correctamente la vista de la consideración.

Considere el siguiente esquema:

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

Todo esto funciona:

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;

Esto falla:

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

Una comparación de los "planes de ejecución estimados":

insert into testdata (kind, data) values ('noninteger', 'noninteger'): ingrese la descripción de la imagen aquí

exec insert_testdata @kind = 'noninteger', @data = 'noninteger': ingrese la descripción de la imagen aquí

cocogorilla
fuente
¿Alguna diferencia notable entre el plan de proceso almacenado ad hoc y en caché por casualidad?
Ali Razeghi
sí, cuando ejecuta ad hoc, no obtiene ningún operador contra la vista indizada ... Creo que sql es lo suficientemente inteligente como para ver que hay un filtro en la vista y eliminarlo de la consideración (esta eliminación no ocurre en el proc)
cocogorilla
44
¿No está en condiciones de probar pero agregar option (recompile)ayuda?
Martin Smith
2
Solo por curiosidad, ¿qué problema estás tratando de resolver? Esto huele a un problema XY .
Max Vernon
1
@MaxVernon Estoy trabajando con una estructura de datos existente y necesito una búsqueda rápida de valores enteros únicos almacenados en un subconjunto de nvarchar (4000), un filtro en otra columna define ese subconjunto de filas.
cocogorilla

Respuestas:

6

Gracias por proporcionar un script completo para recrear el problema.

Lo probé con SQL Server 2014 Express.

Cuando agrego OPTION(RECOMPILE)funciona:

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

Cuando ejecuto esto en SSMS:

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

Recibo este mensaje:

(1 row(s) affected)

y se agrega una fila a la tabla.

¿Qué versión de SQL Server estás usando? Recuerdo vagamente que en versiones anteriores a 2008 esto se OPTION(RECOMPILE)comportó de manera un poco diferente.


Estoy trabajando con una estructura de datos existente y necesito una búsqueda rápida de valores enteros únicos almacenados en un subconjunto de nvarchar (4000), un filtro en otra columna define ese subconjunto de filas.

En este caso, puede ser mejor usar el índice filtrado en lugar de la vista indizada:

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

El optimizador debe usar este índice cuando el WHEREfiltro de la consulta coincida exactamente con la WHEREcláusula del índice.

Sí, aquí el índice está en la nvarcharcolumna, lo que puede no ser lo mejor, especialmente si une esta tabla con una intcolumna de otra tabla, o intenta filtrar valores en esta columna usando intvalores.


Otra variante que viene a la mente es la columna computada persistente que se convierte nvarchara int. En esencia, es muy similar a su vista, pero los nvarcharvalores persistentes que se convierten intse almacenan con la misma tabla, no en un objeto separado.

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

Con esta configuración, intenté usar su procedimiento almacenado original para insertar filas y funcionó incluso sin él OPTION(RECOMPILE).


En realidad, parece que la razón principal por la que funciona la columna persistente anterior es que yo uso CASE. Si agrego CASEa la definición de su vista, el procedimiento almacenado funciona sin él 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
fuente
No estoy seguro de que el índice filtrado funcione bien porque el ancho de columna es 4000 (muy por encima del límite de 900). No había pensado en usar una opción de sugerencia de consulta recompilar ... Estaba aplicando recompilar a todo el procedimiento. ¡Su sugerencia funciona para todos mis casos de prueba! Gracias.
cocogorilla
1
Sí, el índice filtrado en la columna original puede no ser muy útil. Agregué otra variante con columna computada persistente.
Vladimir Baranov
Me encanta la opción de columna computada persistente ... que me parece la solución correcta
cocogorilla