如何创建可选择搜索列的存储过程?

时间:2008-10-15 16:57:17

标签: sql sql-server search stored-procedures parameters

我正在研究将要查询我们员工数据库的工作申请。最终用户希望能够根据标准名称/部门标准进行搜索,但他们也希望能够灵活地查询所有在健康部门工作的名字为“James”的人。我想要避免的一件事是简单地让存储过程获取参数列表并生成要执行的SQL语句,因为这将在内部级别打开SQL注入的大门。

可以这样做吗?

10 个答案:

答案 0 :(得分:17)

虽然COALESCE技巧很简洁,但我首选的方法是:

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 + '%')

这些SP可以很容易地生成代码(并为表更改重新生成)。

您有一些处理数字的选项 - 取决于您是否需要精确的语义或搜索语义。

答案 1 :(得分:10)

实现此类搜索的最有效方法是使用存储过程。此处显示的语句创建一个接受所需参数的过程。如果未提供参数值,则将其设置为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)

摘自此页:http://www.sqlteam.com/article/implementing-a-dynamic-where-clause

我以前做过。效果很好。

答案 2 :(得分:5)

Erland Sommarskog的文章Dynamic Search Conditions in T-SQL是如何做到这一点的一个很好的参考。 Erland提供了许多关于如何在不使用动态SQL(只是简单的IF块,OR,COALESCE等)的情况下执行此操作的策略,甚至列出了每种技术的性能特征。

如果您必须咬紧牙关并浏览动态SQL路径,您还应该阅读Erland的Curse and Blessings of Dynamic SQL,其中提供了有关如何正确编写动态SQL的一些提示

答案 3 :(得分:3)

使用COALESCE方法的问题在于,如果您的列具有NULL值,则传入NULL搜索条件(意味着忽略搜索条件)将不会返回许多数据库中的行。

例如,在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

您只会返回两行,因为在my_string列为NULL的行中,您可以有效地获取:

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

但当然,NULL不等于NULL。

我试着坚持:

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

当然,您可以将其调整为使用外卡或其他任何您想要做的事情。

答案 4 :(得分:2)

可以这样做,但通常这些厨房水槽程序会导致一些糟糕的查询计划。

说了这么多,这里是最常用于“可选”参数的策略。通常的做法是将NULL视为“省略”。

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)

编辑: 一种更好的方法是参数化查询。 以下是来自该领域世界最重要权威之一的博客文章,来自LLBLGen Pro的Frans Bouma成名:

Stored Procedures vs. Dynamic Queries

答案 5 :(得分:0)

从我的博文中复制此内容:

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

答案 6 :(得分:0)

我们可以使用Generic @Search Parameter并将任何值传递给它进行搜索。

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

答案 7 :(得分:-1)

我的第一个想法是写一个像这样的查询......

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
       )

...如果他们想要搜索某个特定字段然后提供一个值,如果他们要搜索它,那么调用者会提供一个标志,但我不知道这是否正在创建一个草率的WHERE如果我可以在WHERE子句中使用CASE语句。

正如您所看到的,这个特定代码是在T-SQL中,但我也很乐意看一些PL-SQL / MySQL代码并相应地进行调整。

答案 8 :(得分:-1)

我会坚持使用NULL / COALESCE方法而不是AdHoc查询,然后进行测试以确保没有性能问题。

如果事实证明你的查询运行缓慢,因为当你搜索索引的列时它正在进行表扫描,你总是可以用通过搜索这些索引字段的其他特定搜索存储过程来补充通用搜索存储过程。 。例如,您可以使用一个特殊的SP来执行CustomerID或Last / First Name的搜索。

答案 9 :(得分:-3)

编写一个程序,在表格中插入名称以A开头的所有员工数据