2008-10-15 13 views
9

Estoy trabajando en una aplicación para el trabajo que va a consultar nuestra base de datos de empleados. Los usuarios finales desean tener la capacidad de buscar según los criterios estándar de nombre/departamento, pero también quieren la flexibilidad para consultar a todas las personas con el nombre de "James" que trabaja en el Departamento de Salud. Lo único que quiero evitar es simplemente hacer que el procedimiento almacenado tome una lista de parámetros y generar una instrucción SQL para ejecutar, ya que eso abriría las puertas a la inyección SQL a nivel interno.¿Cómo creo un procedimiento almacenado que opcionalmente buscará columnas?

¿Se puede hacer esto?

+0

que quería mencionar aquí que la solución de Cade Roux funcionado mejor para mí ya que había una gran cantidad de valores de datos NULL en la tabla de destino, pero no pude ver lo bien que COALESCE funcionaría si tuviera los datos en toda mi columnas, por lo que el voto a favor de la solución de BoltBait definitivamente merece el mérito. –

+1

Aaron Bertrand llama a esto un "Procedimiento de fregadero de cocina" y tiene algunas buenas ideas para tratar este tipo de problema que se pueden ver en http://sqlsentry.tv/the-kitchen-sink-procedure/ y http: // blogs .sqlsentry.com/aaronbertrand/backtobasics-updated-kitchen-sink-example /. – JamieSee

Respuesta

16

Mientras que el COALESCE truco es ordenada, mi método preferido es:

CREATE PROCEDURE ps_Customers_SELECT_NameCityCountry 
    @Cus_Name varchar(30) = NULL 
    ,@Cus_City varchar(30) = NULL 
    ,@Cus_Country varchar(30) = NULL 
    ,@Dept_ID int = NULL 
    ,@Dept_ID_partial varchar(10) = NULL 
AS 
SELECT Cus_Name 
     ,Cus_City 
     ,Cus_Country 
     ,Dept_ID 
FROM Customers 
WHERE (@Cus_Name IS NULL OR Cus_Name LIKE '%' + @Cus_Name + '%') 
     AND (@Cus_City IS NULL OR Cus_City LIKE '%' + @Cus_City + '%') 
     AND (@Cus_Country IS NULL OR Cus_Country LIKE '%' + @Cus_Country + '%') 
     AND (@Dept_ID IS NULL OR Dept_ID = @DeptID) 
     AND (@Dept_ID_partial IS NULL OR CONVERT(varchar, Dept_ID) LIKE '%' + @Dept_ID_partial + '%') 

Este tipo de SP se puede generar fácilmente el código (y re-generado para la tabla-cambios).

Tiene algunas opciones para manejar números, dependiendo de si quiere semántica exacta o semántica de búsqueda.

+0

¿Cómo funcionaría esto para mi campo ID de departamento? ¿Puedo usar '%' para un int o debo especificar una sintaxis diferente? –

+0

Tiene algunas opciones para manejar números, dependiendo de si quiere semántica exacta o semántica de búsqueda. –

+0

Acabo de ver la actualización del procedimiento. ¡¡¡Gracias!!! –

-1

Mi primer pensamiento fue escribir una consulta algo como esto ...

SELECT EmpId, NameLast, NameMiddle, NameFirst, DepartmentName 
    FROM dbo.Employee 
     INNER JOIN dbo.Department ON dbo.Employee.DeptId = dbo.Department.Id 
WHERE IdCrq IS NOT NULL 
     AND 
     (
      @bitSearchFirstName = 0 
      OR 
      Employee.NameFirst = @vchFirstName 
     ) 
     AND 
     (
      @bitSearchMiddleName = 0 
      OR 
      Employee.NameMiddle = @vchMiddleName 
     ) 
     AND 
     (
      @bitSearchFirstName = 0 
      OR 
      Employee.NameLast = @vchLastName 
     ) 
     AND 
     (
      @bitSearchDepartment = 0 
      OR 
      Department.Id = @intDeptID 
     ) 

... que luego tener la persona que llama proporcionar un indicador de bits si quieren buscar un campo en particular y luego suministrar el valor si van a buscarlo, pero no sé si esto está creando una cláusula WHERE descuidada o si puedo salirse con la suya con una sentencia CASE en la cláusula WHERE.

Como puede ver, este código particular está en T-SQL, pero con mucho gusto también veré algún código PL-SQL/MySQL y lo adaptaré en consecuencia.

+0

Me pregunto qué pasa con esta consulta que recibe votos negativos. –

+0

@Teomanshipahi El problema con mi consulta es que requiere dos variables, un bit para buscar en el campo y luego el parámetro en sí, lo que hace que un procedimiento almacenado muy grande se vincule con variables. Al usar coalesce, puede usar una sola variable para cada campo que quiera consultar. Supongo que debería eliminar mi propia respuesta teniendo en cuenta la antigüedad de este hilo 8^D –

9

La forma más eficiente de implementar este tipo de búsqueda es con un procedimiento almacenado. La declaración que se muestra aquí crea un procedimiento que acepta los parámetros requeridos. Cuando no se proporciona un valor de parámetro, se establece en NULL.

CREATE PROCEDURE ps_Customers_SELECT_NameCityCountry 
@Cus_Name varchar(30) = NULL, 
@Cus_City varchar(30) = NULL, 
@Cus_Country varchar(30) =NULL 
AS 
SELECT Cus_Name, 
     Cus_City, 
     Cus_Country 
FROM Customers 
WHERE Cus_Name = COALESCE(@Cus_Name,Cus_Name) AND 
     Cus_City = COALESCE(@Cus_City,Cus_City) AND 
     Cus_Country = COALESCE(@Cus_Country,Cus_Country) 

Tomado de esta página: http://www.sqlteam.com/article/implementing-a-dynamic-where-clause

lo he hecho antes. Funciona bien.

+0

No creo que estos funcionen bien. Obtiene un gran porcentaje de escaneos de tabla, porque esos predicados no son SARGables. –

+0

Cuando dice que el valor se establece en NULO, ¿quiere decir que busca NULL en el nombre de la columna o se ignora? La única preocupación que veo es que si busco gente con el apellido Schmoe, el nombre de "Joe" se excluirá porque el valor no es NULO. –

+0

Dillie-O, busque el comando COALESCE para ver por qué funciona esto (o siga el enlace proporcionado en mi publicación).En cuanto al rendimiento de este tipo de cosas ... el sistema en el que lo implementé tenía entre 1 y 2 millones de filas y funcionó bien. No parecía lento en absoluto. YMMV. – BoltBait

2

Se puede hacer, pero generalmente estos procedimientos de fregadero de cocina resultan en algunos planes de consulta deficientes.

Habiendo dicho todo eso, esta es la táctica más comúnmente utilizada para los parámetros "opcionales". El enfoque normal es tratar a NULL como "omitido".

SELECT 
    E.EmployeeID, 
    E.LastName, 
    E.FirstName 
WHERE 
    E.FirstName = COALESCE(@FirstName, E.FirstName) AND 
    E.LastName = COALESCE(@LastName, E.LastName) AND 
    E.DepartmentID = COALESCE(@DepartmentID, E.DepartmentID) 

EDIT: un mejor enfoque sería consultas con parámetros. Aquí es un blog de una de las primeras autoridades del mundo en este ámbito, Frans Bouma de LLBLGen Pro fama: el artículo de

Stored Procedures vs. Dynamic Queries

+0

En la respuesta anterior, dijiste que estos no funcionan tan bien, ¿crees que acabo de dar un respiro, hago un montón de insumos para desinfectar y creo una consulta AdHoc, o creo procedimientos más especializados para todas las diferentes opciones? –

+0

Bueno, esta es una de las razones por las cuales los sistemas ORM (Modelo relacional de objetos) están entrando en boga realmente. La mayoría usa generación dinámica de consultas, pero usan la parametrización para no tener problemas de inyección. Mira las consultas parametrizadas. Eso podría servirle mejor en este caso. –

5

Erland Sommarskog Dynamic Search Conditions in T-SQL es una buena referencia sobre cómo hacer esto. Erland presenta una serie de estrategias sobre cómo hacer esto sin usar SQL dinámico (simplemente bloques IF, O, COALESCE, etc.) e incluso enumera las características de rendimiento de cada técnica.

en caso de tener que morder la bala y pasar por el camino de SQL dinámico, también debe leer de Erland Curse and Blessings of Dynamic SQL donde da algunos consejos sobre cómo escribir correctamente LSQ dinámicas

+0

¡Grandes referencias! Siempre disfruto el trabajo de Erland Sommarskog. –

-1

Me quedaría con el método NULL/COALESCE sobre AdHoc Queries, y luego probaré para asegurarme de que no tenga problemas de rendimiento.

Si resulta que tiene consultas de ejecución lenta porque está realizando una exploración de tabla cuando está buscando en columnas que están indexadas, siempre puede complementar el procedimiento almacenado de búsqueda genérica con específicas adicionales que permiten buscar en estas indexadas campos. Por ejemplo, podría tener un SP especial que realice búsquedas por ID de cliente o Apellido/Nombre.

+0

He trabajado con aplicaciones de CRM donde la pantalla de búsqueda tiene 30 campos. Las permutaciones funcionan contra ti allí. Los ORM brillan en esta área. –

+0

No sugiero que crees un SP diferente para cada permutación. (Eso sería 2^30 procedimientos almacenados). ¿No estaría de acuerdo con que tiene sentido tener un SP de caso especial en caso de que un usuario busque por CustomerID (que supongo que es único)? – Aheho

+0

De nuevo, solo estoy sugiriendo esto si encuentra que el método COALESCE produce planes de consulta subóptimos si CustomerID está presente – Aheho

3

El método COALESCE tiene un problema porque si su columna tiene un valor NULL, pasar una condición de búsqueda NULA (es decir, ignorar la condición de búsqueda) no devolverá la fila en muchas bases de datos.

Por ejemplo, tratar el siguiente código en SQL Server 2000:

CREATE TABLE dbo.Test_Coalesce (
    my_id INT NOT NULL IDENTITY, 
    my_string VARCHAR(20) NULL) 
GO 
INSERT INTO dbo.Test_Coalesce (my_string) VALUES (NULL) 
INSERT INTO dbo.Test_Coalesce (my_string) VALUES ('t') 
INSERT INTO dbo.Test_Coalesce (my_string) VALUES ('x') 
INSERT INTO dbo.Test_Coalesce (my_string) VALUES (NULL) 
GO 
DECLARE @my_string VARCHAR(20) 
SET @my_string = NULL 
SELECT * FROM dbo.Test_Coalesce WHERE my_string = COALESCE(@my_string, my_string) 
GO 

Sólo se pondrá en contacto dos filas porque en las filas donde el mi_cadena columna es NULL que son eficaces conseguir:

my_string = COALESCE(@my_string, my_string) => 
my_string = COALESCE(NULL, my_string) => 
my_string = my_string => 
NULL = NULL 

Pero, por supuesto, NULL no es igual a NULL.

trato de seguir con:

SELECT 
    my_id, 
    my_string 
FROM 
    dbo.Test_Coalesce 
WHERE 
    (@my_string IS NULL OR my_string = @my_string) 

Por supuesto, puede ajustar que para usar comodines o cualquier otra cosa que usted quiere hacer.

+0

Buena aclaración. –

0

copia esto desde mi blog:

USE [AdventureWorks] 
GO 

CREATE PROCEDURE USP_GET_Contacts_DynSearch 
(
    -- Optional Filters for Dynamic Search 
    @ContactID   INT = NULL, 
    @FirstName   NVARCHAR(50) = NULL, 
    @LastName   NVARCHAR(50) = NULL, 
    @EmailAddress  NVARCHAR(50) = NULL, 
    @EmailPromotion  INT = NULL, 
    @Phone    NVARCHAR(25) = NULL 
) 
AS 
BEGIN 
    SET NOCOUNT ON 

    DECLARE 
     @lContactID   INT, 
     @lFirstName   NVARCHAR(50), 
     @lLastName   NVARCHAR(50), 
     @lEmailAddress  NVARCHAR(50), 
     @lEmailPromotion INT, 
     @lPhone    NVARCHAR(25) 

    SET @lContactID   = @ContactID 
    SET @lFirstName   = LTRIM(RTRIM(@FirstName)) 
    SET @lLastName   = LTRIM(RTRIM(@LastName)) 
    SET @lEmailAddress  = LTRIM(RTRIM(@EmailAddress)) 
    SET @lEmailPromotion = @EmailPromotion 
    SET @lPhone    = LTRIM(RTRIM(@Phone)) 

    SELECT 
     ContactID, 
     Title, 
     FirstName, 
     MiddleName, 
     LastName, 
     Suffix, 
     EmailAddress, 
     EmailPromotion, 
     Phone 
    FROM [Person].[Contact] 
    WHERE 
     (@lContactID IS NULL OR ContactID = @lContactID) 
    AND (@lFirstName IS NULL OR FirstName LIKE '%' + @lFirstName + '%') 
    AND (@lLastName IS NULL OR LastName LIKE '%' + @lLastName + '%') 
    AND (@lEmailAddress IS NULL OR EmailAddress LIKE '%' + @lEmailAddress + '%') 
    AND (@lEmailPromotion IS NULL OR EmailPromotion = @lEmailPromotion) 
    AND (@lPhone IS NULL OR Phone = @lPhone) 
    ORDER BY ContactID 

END 
GO 
-3

escribir un procedimiento para insertar todos los datos de los empleados cuyo nombre comience con A en la tabla ??

0

Podemos utilizar el parámetro @Search genérico y pasarle cualquier valor para la búsqueda.

GO 
SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 
-- ============================================= 
-- Author: -- 
-- Create date: 
-- Description: -- 
-- ============================================= 
CREATE PROCEDURE [dbo].[usp_StudentList] 
    @PageNumber INT = 1, -- Paging parameter 
    @PageSize INT = 10,-- Paging parameter 
    @Search VARCHAR(MAX) = NULL, --Generic Search Parameter 
    @OrderBy VARCHAR(MAX) = 'FirstName', --Default Column Name 'FirstName' for records ordering 
    @SortDir VARCHAR(MAX) = 'asc' --Default ordering 'asc' for records ordering 
AS 
BEGIN 
    SET NOCOUNT ON; 

    --Query required for paging, this query used to show total records 
    SELECT COUNT(StudentId) AS RecordsTotal FROM Student 

    SELECT Student.*, 
     --Query required for paging, this query used to show total records filtered 
     COUNT(StudentId) OVER (PARTITION BY 1) AS RecordsFiltered 
    FROM Student 
    WHERE 
    --Generic Search 
    -- Below is the column list to add in Generic Serach 
    (@Search IS NULL OR Student.FirstName LIKE '%'+ @Search +'%') 
    OR (@Search IS NULL OR Student.LastName LIKE '%'+ @Search +'%') 
    --Order BY 
    -- Below is the column list to allow sorting 
    ORDER BY 
    CASE WHEN @SortDir = 'asc' AND @OrderBy = 'FirstName' THEN Student.FirstName END, 
    CASE WHEN @SortDir = 'desc' AND @OrderBy = 'FirstName' THEN Student.FirstName END DESC, 
    CASE WHEN @SortDir = 'asc' AND @OrderBy = 'LastName' THEN Student.LastName END, 
    CASE WHEN @SortDir = 'desc' AND @OrderBy = 'LastName' THEN Student.LastName END DESC, 
    OFFSET @PageSize * (@PageNumber - 1) ROWS FETCH NEXT @PageSize ROWS ONLY; 
END 
Cuestiones relacionadas