¿Cómo puedo usar parámetros opcionales en un procedimiento almacenado de T-SQL?
Estoy creando un procedimiento almacenado para hacer una búsqueda a través de una tabla. Tengo muchos campos de búsqueda diferentes, todos los cuales son opcionales. ¿Hay alguna manera de crear un procedimiento almacenado que maneje esto? Digamos que tengo una tabla con cuatro campos: ID, FirstName, LastName y Title. Podría hacer algo como esto:
CREATE PROCEDURE spDoSearch
@FirstName varchar(25) = null,
@LastName varchar(25) = null,
@Title varchar(25) = null
AS
BEGIN
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
FirstName = ISNULL(@FirstName, FirstName) AND
LastName = ISNULL(@LastName, LastName) AND
Title = ISNULL(@Title, Title)
END
Este tipo de obras. Sin embargo ignora los registros donde FirstName, LastName o Title son NULL. Si el título no está especificado en los parámetros de búsqueda que quiero incluir registros donde Title es NULL-lo mismo para FirstName y LastName. Sé que probablemente podría hacer esto con SQL dinámico, pero me gustaría evitar eso.
6 answers
Cambiar dinámicamente las búsquedas basadas en los parámetros dados es un tema complicado y hacerlo de una manera sobre otra, incluso con una diferencia muy leve, puede tener implicaciones de rendimiento masivas. La clave es usar un índice, ignorar código compacto, ignorar preocuparse por repetir código, debe hacer un buen plan de ejecución de consultas (use un índice).
Lea esto y considere todos los métodos. Su mejor método dependerá de sus parámetros, sus datos, su esquema y su uso:
Condiciones de búsqueda dinámica en T-SQL por by Erland Sommarskog
La Maldición y las Bendiciones de SQL Dinámico por Erland Sommarskog
Si tiene la versión correcta de SQL Server 2008 (SQL 2008 SP1 CU5 (10.0.2746) y posteriores), puede usar este pequeño truco para usar un índice:
Agregue OPTION (RECOMPILE)
a su consulta, consulte el artículo de Erland , y SQL Server resolverá el OR
desde (@LastName IS NULL OR LastName= @LastName)
antes de que se cree el plan de consulta basado en se pueden usar los valores de tiempo de ejecución de las variables locales y un índice.
Esto funcionará para cualquier versión de SQL Server (devuelve los resultados adecuados), pero solo incluye la OPCIÓN(RECOMPILAR) si estás en SQL 2008 SP1 CU5 (10.0.2746) y posteriores. La OPCIÓN(RECOMPILAR) recompilará su consulta, solo la versión listada la recompilará en función de los valores de tiempo de ejecución actuales de las variables locales, lo que le dará el mejor rendimiento. Si no está en esa versión de SQL Server 2008, simplemente deje eso línea fuera.
CREATE PROCEDURE spDoSearch
@FirstName varchar(25) = null,
@LastName varchar(25) = null,
@Title varchar(25) = null
AS
BEGIN
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
(@FirstName IS NULL OR (FirstName = @FirstName))
AND (@LastName IS NULL OR (LastName = @LastName ))
AND (@Title IS NULL OR (Title = @Title ))
OPTION (RECOMPILE) ---<<<<use if on for SQL 2008 SP1 CU5 (10.0.2746) and later
END
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-25 09:43:20
La respuesta de @KM es buena hasta donde llega, pero no da seguimiento completo a uno de sus primeros consejos;
..., ignorar código compacto, ignorar preocuparse por repetir código, ...
Si está buscando lograr el mejor rendimiento, debe escribir una consulta a medida para cada posible combinación de criterios opcionales. Esto puede sonar extremo, y si tiene muchos criterios opcionales, entonces podría serlo, pero el rendimiento a menudo es una compensación entre esfuerzo y resultados. En la práctica, puede haber un conjunto común de combinaciones de parámetros que se pueden dirigir con consultas a medida, luego una consulta genérica (según las otras respuestas) para todas las demás combinaciones.
CREATE PROCEDURE spDoSearch
@FirstName varchar(25) = null,
@LastName varchar(25) = null,
@Title varchar(25) = null
AS
BEGIN
IF (@FirstName IS NOT NULL AND @LastName IS NULL AND @Title IS NULL)
-- Search by first name only
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
FirstName = @FirstName
ELSE IF (@FirstName IS NULL AND @LastName IS NOT NULL AND @Title IS NULL)
-- Search by last name only
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
LastName = @LastName
ELSE IF (@FirstName IS NULL AND @LastName IS NULL AND @Title IS NOT NULL)
-- Search by title only
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
Title = @Title
ELSE IF (@FirstName IS NOT NULL AND @LastName IS NOT NULL AND @Title IS NULL)
-- Search by first and last name
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
FirstName = @FirstName
AND LastName = @LastName
ELSE
-- Search by any other combination
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
(@FirstName IS NULL OR (FirstName = @FirstName))
AND (@LastName IS NULL OR (LastName = @LastName ))
AND (@Title IS NULL OR (Title = @Title ))
END
La ventaja de este enfoque es que en los casos comunes manejados por consultas a medida, la consulta es tan eficiente como puede ser - no hay impacto por los criterios no aplicados. Además, los índices y otras mejoras de rendimiento se pueden dirigir a consultas específicas a medida en lugar de que tratar de satisfacer todas las situaciones posibles.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-25 19:19:26
Puede hacer en el siguiente caso,
CREATE PROCEDURE spDoSearch
@FirstName varchar(25) = null,
@LastName varchar(25) = null,
@Title varchar(25) = null
AS
BEGIN
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
(@FirstName IS NULL OR FirstName = @FirstName) AND
(@LastNameName IS NULL OR LastName = @LastName) AND
(@Title IS NULL OR Title = @Title)
END
Sin embargo dependen de los datos a veces mejor crear consultas dinámicas y ejecutarlas.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-05 14:35:20
Extiende tu WHERE
condición:
WHERE
(FirstName = ISNULL(@FirstName, FirstName)
OR COALESCE(@FirstName, FirstName, '') = '')
AND (LastName = ISNULL(@LastName, LastName)
OR COALESCE(@LastName, LastName, '') = '')
AND (Title = ISNULL(@Title, Title)
OR COALESCE(@Title, Title, '') = '')
Es decir, combinar diferentes casos con condiciones booleanas.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-31 12:25:13
Cinco años de retraso en la fiesta.
Se menciona en los enlaces proporcionados de la respuesta aceptada, pero creo que merece una respuesta explícita sobre la construcción dinámica de la consulta basada en los parámetros proporcionados. Por ejemplo:
Setup
-- drop table Person
create table Person
(
PersonId INT NOT NULL IDENTITY(1, 1) CONSTRAINT PK_Person PRIMARY KEY,
FirstName NVARCHAR(64) NOT NULL,
LastName NVARCHAR(64) NOT NULL,
Title NVARCHAR(64) NULL
)
GO
INSERT INTO Person (FirstName, LastName, Title)
VALUES ('Dick', 'Ormsby', 'Mr'), ('Serena', 'Kroeger', 'Ms'),
('Marina', 'Losoya', 'Mrs'), ('Shakita', 'Grate', 'Ms'),
('Bethann', 'Zellner', 'Ms'), ('Dexter', 'Shaw', 'Mr'),
('Zona', 'Halligan', 'Ms'), ('Fiona', 'Cassity', 'Ms'),
('Sherron', 'Janowski', 'Ms'), ('Melinda', 'Cormier', 'Ms')
GO
Procedimiento
ALTER PROCEDURE spDoSearch
@FirstName varchar(64) = null,
@LastName varchar(64) = null,
@Title varchar(64) = null,
@TopCount INT = 100
AS
BEGIN
DECLARE @SQL NVARCHAR(4000) = '
SELECT TOP ' + CAST(@TopCount AS VARCHAR) + ' *
FROM Person
WHERE 1 = 1'
PRINT @SQL
IF (@FirstName IS NOT NULL) SET @SQL = @SQL + ' AND FirstName = @FirstName'
IF (@LastName IS NOT NULL) SET @SQL = @SQL + ' AND FirstName = @LastName'
IF (@Title IS NOT NULL) SET @SQL = @SQL + ' AND Title = @Title'
EXEC sp_executesql @SQL, N'@TopCount INT, @FirstName varchar(25), @LastName varchar(25), @Title varchar(64)',
@TopCount, @FirstName, @LastName, @Title
END
GO
Uso
exec spDoSearch @TopCount = 3
exec spDoSearch @FirstName = 'Dick'
Ventajas:
- fácil de escribir y entender
- flexibilidad: genere fácilmente la consulta para filtraciones más complicadas (por ejemplo, dinámicas TOP)
Contras:
- posibles problemas de rendimiento en función de los parámetros, índices y volumen de datos proporcionados
No es una respuesta directa, sino relacionada con el problema, también conocido como el panorama general
Por lo general, estos procedimientos almacenados de filtrado no flotan alrededor, sino que se llaman desde alguna capa de servicio. Esto deja la opción de mover la lógica de negocio (filtrado) de SQL a la capa de servicio.
Un ejemplo es usar LINQ2SQL para generar la consulta basada en los filtros proporcionados:
public IList<SomeServiceModel> GetServiceModels(CustomFilter filters)
{
var query = DataAccess.SomeRepository.AllNoTracking;
// partial and insensitive search
if (!string.IsNullOrWhiteSpace(filters.SomeName))
query = query.Where(item => item.SomeName.IndexOf(filters.SomeName, StringComparison.OrdinalIgnoreCase) != -1);
// filter by multiple selection
if ((filters.CreatedByList?.Count ?? 0) > 0)
query = query.Where(item => filters.CreatedByList.Contains(item.CreatedById));
if (filters.EnabledOnly)
query = query.Where(item => item.IsEnabled);
var modelList = query.ToList();
var serviceModelList = MappingService.MapEx<SomeDataModel, SomeServiceModel>(modelList);
return serviceModelList;
}
Ventajas:
- consulta generada dinámicamente basada en filtros proporcionados. No parámetro sniffing o recompile pistas necesarias
- algo más fácil de escribir para aquellos en el mundo OOP
- normalmente amigable con el rendimiento, ya que se emitirán consultas "simples" (sin embargo, aún se necesitan índices apropiados)
Contras:
- Se pueden alcanzar limitaciones de LINQ2QL y forzar un degradar a LINQ2Objects o volver a la solución SQL pura dependiendo del caso
- la escritura descuidada de LINQ podría generar consultas horribles (o muchas consultas, si se cargan las propiedades de navegación)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-19 15:40:36
Esto también funciona:
...
WHERE
(FirstName IS NULL OR FirstName = ISNULL(@FirstName, FirstName)) AND
(LastName IS NULL OR LastName = ISNULL(@LastName, LastName)) AND
(Title IS NULL OR Title = ISNULL(@Title, Title))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-17 16:10:32