代码之家  ›  专栏  ›  技术社区  ›  Dillie-O

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

  •  9
  • Dillie-O  · 技术社区  · 16 年前

    我正在开发一个工作应用程序,它将查询我们的员工数据库。最终用户希望能够根据标准的姓名/部门标准进行搜索,但他们也希望能够灵活地查询所有在卫生部门工作的名字为“james”的人。我要避免的一件事就是让存储过程获取参数列表并生成一条要执行的SQL语句,因为这将在内部级别打开SQL注入的大门。

    能做到吗?

    10 回复  |  直到 7 年前
        1
  •  17
  •   Cade Roux    16 年前

    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可以很容易地生成代码(并为表更改重新生成)。

    您有几个处理数字的选项——这取决于您想要确切的语义还是搜索语义。

        2
  •  10
  •   BoltBait    16 年前

    实现这种类型搜索的最有效方法是使用存储过程。这里显示的语句创建一个接受所需参数的过程。如果未提供参数值,则将其设置为空。

    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

    我以前做过。它运作良好。

        3
  •  5
  •   jop    16 年前

    Erland Sommarskog的文章 Dynamic Search Conditions in T-SQL 是如何做到这一点的很好的参考。Erland提供了一些策略,说明如何在不使用动态SQL的情况下实现这一点(只是简单的if块、or、coalesce等),甚至列出了每种技术的性能特征。

    如果您必须咬紧牙关并通过动态SQL路径,您还应该阅读erland的 Curse and Blessings of Dynamic SQL 在那里他给出了一些关于如何正确地编写动态SQL的提示

        4
  •  3
  •   Tom H    16 年前

    使用coalesce方法有一个问题,即如果列的值为空,则传入空搜索条件(即忽略搜索条件)将不会返回许多数据库中的行。

    例如,在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 = COALESCE(@my_string, my_string) =>
    my_string = COALESCE(NULL, my_string) =>
    my_string = my_string =>
    NULL = NULL
    

    但是,当然,空不等于空。

    我努力坚持:

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

    当然,你可以把它调整为使用外卡或者其他你想做的事情。

        5
  •  2
  •   Pittsburgh DBA    16 年前

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

    尽管如此,这里还是最常用于“可选”参数的策略。通常的方法是将空值视为“ommitted”。

    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 fame的frans bouma:

    Stored Procedures vs. Dynamic Queries

        6
  •  0
  •   Taryn Hemanshu    9 年前

    从我的日志中复制此内容:

    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
    
        7
  •  0
  •   Ravindra Vairagi    7 年前

    我们可以使用泛型@search参数,并将任何值传递给它进行搜索。

    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
    
        8
  •  -1
  •   Dillie-O    16 年前

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

    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代码,并相应地进行调整。

        9
  •  -1
  •   Aheho    16 年前

    我将坚持使用空/合并方法来处理特殊查询,然后测试以确保没有性能问题。

    如果发现查询运行缓慢,因为在对索引列进行搜索时,它正在进行表扫描,则可以使用允许对这些索引字段进行搜索的其他特定过程来补充通用搜索存储过程。例如,您可以有一个特殊的SP,它可以按customerid或姓氏/名字进行搜索。

        10
  •  -3
  •   Raaj    8 年前

    编写一个过程来插入名称以in表开头的所有员工数据??