2010-08-05 18 views
147

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, Nombre, Apellido y Título. Podría hacer algo como esto:¿Cómo puedo usar parámetros opcionales en un procedimiento almacenado de T-SQL?

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 no se especifica el título en los parámetros de búsqueda, quiero incluir registros donde Title sea NULL, lo mismo para FirstName y LastName. Sé que probablemente podría hacer esto con SQL dinámico, pero me gustaría evitar eso.

+0

un vistazo aquí: http://stackoverflow.com/questions/11396919/building-dynamic-where-clause -in-stored-procedure/25473624 # 25473624 –

+1

Intente seguir la instrucción where: 'code' ISNULL (FirstName, ') = ISNULL (@FirstName,' ') - esto hará que cada NULL sea una cadena vacía y aquellos se puede comparar a través de eq. operador. Si desea obtener todos los títulos si el parámetro de entrada es nulo, intente algo como eso: 'code'FirstName = @FirstName OR @FirstName IS NULL. – baHI

Respuesta

214

Las búsquedas dinámicamente cambiantes basadas en los parámetros dados son un tema complicado y hacerlo de una forma u otra, incluso con una pequeña diferencia, puede tener implicaciones de rendimiento masivo. La clave es usar un índice, ignorar el código compacto, ignorar la preocupación por repetir el código, debe hacer un buen plan de ejecución de la consulta (usar 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 real:

Dynamic Search Conditions in T-SQL by by Erland Sommarskog

The Curse and Blessings of Dynamic SQL by Erland Sommarskog

Si usted tiene la versión correcta de SQL Server 2008 (SQL 2008 SP1 5 um (10,0 0,2746) y más tarde), puede utilizar este pequeño truco para utilizar realmente un índice:

Añadir OPTION (RECOMPILE) en su consulta, see Erland's article y SQL Server resolverá el OR desde dentro (@LastName IS NULL OR LastName= @LastName) antes de que el plan de consulta es creado en base a los valores de tiempo de ejecución de las variables locales, y se puede usar un índice.

Esto funcionará para cualquier versión de SQL Server (devuelva resultados adecuados), pero solo incluya la OPCIÓN (RECOMPRA) si está en SQL 2008 SP1 CU5 (10.0.2746) y posterior. La OPCIÓN (RECOMPRA) recompilará su consulta, solo el verificador enumerado lo recompilará en función de los valores de tiempo de ejecución actuales de las variables locales, lo que le proporcionará el mejor rendimiento. Si no está en esa versión de SQL Server 2008, simplemente deje esa línea.

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 
+12

Tenga cuidado con la precedencia Y/O. Y tiene precedencia sobre O, así que sin los corchetes apropiados este ejemplo no producirá los resultados esperados ... Por lo tanto, debe leer: (@FirstName IS NULL OR (FirstName = @FirstName)) AND (@LastNameIS NULL OR (LastName = @LastName)) AND (@TitleIS NULL OR (Título = @Title)) – Bliek

+0

@Bliek, gracias, lo arreglé. –

+3

Tu respuesta es genial. ¡Gracias por toda la información! –

18

Puede hacerlo 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 crear una mejor consulta dinámica y ejecutarlos.

6

ampliar su 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, '') = '') 

i. mi. combina diferentes casos con condiciones booleanas.

-3

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

La respuesta de @KM es bueno como de lo que cabe, pero no sigue completamente arriba en uno de sus primeros bits de asesoramiento;

..., ignore el código compacto, ignore la preocupación por repetir el código, ...

Si busca obtener 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 ser, pero el rendimiento a menudo es una compensación entre el esfuerzo y los resultados. En la práctica, puede haber un conjunto común de combinaciones de parámetros que se pueden orientar 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 más comunes a medida que maneja consulta la consulta es tan eficiente como puede ser - no hay impacto en los criterios desabastecidas. Además, los índices y otras mejoras de rendimiento se pueden orientar a consultas personalizadas específicas en lugar de intentar satisfacer todas las situaciones posibles.

2

Cinco años tarde a la fiesta.

Se menciona en los enlaces provistos de la respuesta aceptada, pero creo que merece una respuesta explícita sobre SO - construyendo dinámicamente la consulta en función de los parámetros proporcionados. Ej .:

Configuración

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

Pros:

  • fácil de escribir y entender
  • flexibilidad - generar fácilmente la consulta para filtrados más difíciles (por ejemplo, TOP dinámico)

Contras:

  • posibles problemas de rendimiento en función de los parámetros previstos, índices y volumen de datos

No es una respuesta directa, pero relacionado con el problema conocido como el cuadro grande

Normalmente, estos procedimientos almacenados de filtrado no flotan, pero se llaman desde alguna capa de servicio. Esto deja la opción de alejar la lógica comercial (filtrado) de SQL a la capa de servicio.

Un ejemplo es el uso LINQ2SQL para generar la consulta basada en 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; 
    } 

Pros:

consulta
  • generado dinámicamente basado en filtros proporcionados.No hay parameter sniffing o recompile consejos necesarios
  • algo más fácil de escribir para aquellos en el mundo POO
  • normalmente el rendimiento de usar, ya que se emitirán las preguntas "simples" (índices adecuados aún se necesitan sin embargo)

Contras:

  • limitaciones LINQ2QL se pueden alcanzar y forzar una rebaja a LINQ2Objects o regresar a la solución pura de SQL según el caso
  • escritura descuidada de LINQ podría generar consultas terribles (o muchas consultas, si las propiedades de navegación embarcada)
Cuestiones relacionadas