¿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.

Author: dckuehn, 2010-08-05

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
 235
Author: KM.,
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.

 24
Author: Rhys Jones,
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.

 21
Author: Michael Pakhantsov,
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.

 7
Author: devio,
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)
 4
Author: Alexei,
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))
 -3
Author: v2h,
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