我正在开发一个将查询我们的员工数据库的工作应用程序。最终用户希望能够根据标准姓名/部门标准进行搜索,但他们还希望能够灵活地查询在卫生部门工作的所有名字为“James”的人。我要避免的一件事是简单地让存储过程获取参数列表并生成要执行的 SQL 语句,因为这将为内部级别的 SQL 注入打开大门。
这可以做到吗?
我正在开发一个将查询我们的员工数据库的工作应用程序。最终用户希望能够根据标准姓名/部门标准进行搜索,但他们还希望能够灵活地查询在卫生部门工作的所有名字为“James”的人。我要避免的一件事是简单地让存储过程获取参数列表并生成要执行的 SQL 语句,因为这将为内部级别的 SQL 注入打开大门。
这可以做到吗?
虽然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 可以很容易地生成代码(并为表更改重新生成)。
您有几个处理数字的选项 - 取决于您想要精确语义还是搜索语义。
实现此类搜索的最有效方法是使用存储过程。此处显示的语句创建一个接受所需参数的过程。如果未提供参数值,则将其设置为 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
我以前做过。它运作良好。
Erland Sommarskog 的文章Dynamic Search Conditions in T-SQL是关于如何执行此操作的一个很好的参考。Erland 提出了一些关于如何在不使用动态 SQL 的情况下做到这一点的策略(只是普通的 IF 块、OR、COALESCE 等),甚至列出了每种技术的性能特征。
如果您必须硬着头皮走动态 SQL 路径,您还应该阅读 Erland 的动态 SQL 的诅咒和祝福,其中他给出了一些关于如何正确编写动态 SQL 的提示
可以这样做,但通常这些厨房水槽程序会导致一些糟糕的查询计划。
说了这么多,这里是最常用于“可选”参数的策略。正常的方法是将 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 的博客文章:
使用 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)
当然,您可以对其进行调整以使用通配符或您想做的任何其他事情。
从我的博客文章中复制此内容:
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
我们可以使用通用 @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
我的第一个想法是写一个这样的查询......
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 代码并相应地进行调整。
我会坚持使用 NULL/COALESCE 方法而不是 AdHoc 查询,然后进行测试以确保您没有性能问题。
如果事实证明您的查询运行速度很慢,因为在您搜索索引列时它正在执行表扫描,那么您始终可以使用允许在这些索引字段上搜索的其他特定存储过程来补充通用搜索存储过程。例如,您可以有一个特殊的 SP,它按 CustomerID 或姓/名进行搜索。
编写一个程序,将所有名称以A开头的员工数据插入表中??