El índice está creado, la consulta filtra por esa columna, y el plan de ejecución sigue mostrando un Index Scan. Las tres causas que me encuentro una y otra vez:
1. Función aplicada sobre la columna
-- Scan: la función se evalúa fila por fila
WHERE YEAR(DocDate) = 2026
-- Seek: el rango se puede buscar en el índice
WHERE DocDate >= '2026-01-01' AND DocDate < '2027-01-01'Lo mismo con UPPER(Nombre), LEFT(Codigo, 3) o ISNULL(Campo, ''). En cuanto envuelves la columna en una función, el índice deja de servir.
2. Tipos que no coinciden
Si la columna es VARCHAR y comparas contra un NVARCHAR, SQL Server hace una conversión implícita de la columna, no del parámetro. Y eso es, otra vez, una función sobre la columna.
Se ve en el plan como CONVERT_IMPLICIT con un aviso amarillo que es fácil pasar por alto. Es una causa clásica cuando la aplicación está en .NET y los parámetros salen como NVARCHAR por defecto.
3. El índice no cubre y el salto sale caro
Si el índice tiene la columna del filtro pero la consulta pide otras seis columnas, SQL Server calcula el costo de hacer seek + key lookup por cada fila. Si estima que devolverá muchas filas, decide que hacer scan de toda la tabla sale más barato — y suele tener razón.
La solución es un INCLUDE con las columnas que la consulta necesita:
CREATE INDEX IX_OINV_DocDate
ON OINV (DocDate)
INCLUDE (CardCode, DocTotal, DocStatus);El orden en que reviso
- ¿Hay una función sobre la columna filtrada? (incluidas las implícitas)
- ¿El plan muestra
CONVERT_IMPLICIT? - ¿Cuántas filas estima el plan frente a las que realmente devuelve?
Si la estimación está muy lejos de la realidad, el problema no es el índice: son las estadísticas.
Si esto te resultó útil o tienes un caso parecido en tu empresa, escríbeme: me gusta conversar sobre estos problemas aunque no termine en un proyecto.