Как мне создать хранимую процедуру, которая будет необязательно искать столбцы? - PullRequest
9 голосов
/ 15 октября 2008

Я работаю над приложением для работы, которое будет запрашивать базу данных наших сотрудников. Конечные пользователи хотят иметь возможность поиска на основе стандартных критериев имени / отдела, но им также нужна гибкость для запроса всех людей с именем «Джеймс», которое работает в департаменте здравоохранения. Единственное, чего я хочу избежать, - это просто заставить хранимую процедуру взять список параметров и сгенерировать оператор SQL для выполнения, поскольку это откроет двери для внедрения SQL на внутреннем уровне.

Можно ли это сделать?

Ответы [ 10 ]

17 голосов
/ 15 октября 2008

Хотя трюк 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 могут легко генерироваться кодом (и повторно генерироваться для изменений таблицы).

У вас есть несколько вариантов обработки чисел - в зависимости от того, хотите ли вы точную семантику или семантику поиска.

10 голосов
/ 15 октября 2008

Наиболее эффективный способ реализовать этот тип поиска - это хранимая процедура. Показанный здесь оператор создает процедуру, которая принимает обязательные параметры. Если значение параметра не указано, для него устанавливается значение 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

Я делал это раньше. Хорошо работает.

5 голосов
/ 15 октября 2008

Статья Эрланда Соммарскога Условия динамического поиска в T-SQL - хороший справочник о том, как это сделать. Эрланд представляет ряд стратегий о том, как сделать это без использования динамического SQL (просто простые блоки IF, OR, COALESCE и т. Д.), И даже перечисляет характеристики производительности каждого метода.

В случае, если вам нужно прикусить пулю и пройти путь динамического SQL, вам также следует прочитать Проклятие и благословения динамического SQL Эрланда , где он дает некоторые советы о том, как правильно писать динамические SQL

3 голосов
/ 15 октября 2008

Использование метода 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)

Конечно, вы можете настроить это, чтобы использовать подстановочные знаки или что-то еще, что вы хотите сделать.

2 голосов
/ 15 октября 2008

Это может быть сделано, но обычно эти кухонные процедуры приводят к плохим планам запросов.

Сказав все это, вот тактика, наиболее часто используемая для «необязательных» параметров. Обычный подход состоит в том, чтобы рассматривать 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)

EDIT: Гораздо лучший подход - параметризованные запросы. Вот запись в блоге одного из ведущих мировых авторитетов в этой области, Франса Боума из известности LLBLGen Pro:

Хранимые процедуры и динамические запросы

0 голосов
/ 17 января 2018

Мы можем использовать общий параметр @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
0 голосов
/ 17 февраля 2016

Напишите процедуру для вставки всех данных о сотрудниках, чье имя начинается с A в таблице ??

0 голосов
/ 12 февраля 2015

Копирую это из моего блога:

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
0 голосов
/ 15 октября 2008

Я бы придерживался метода NULL / COALESCE над AdHoc Queries, а затем протестировал, чтобы убедиться, что у вас нет проблем с производительностью.

Если выясняется, что у вас медленные запросы, потому что при поиске по индексированным столбцам выполняется сканирование таблицы, вы всегда можете дополнить общую хранимую процедуру поиска дополнительными конкретными, которые позволяют выполнять поиск по этим индексированным полям. , Например, у вас может быть специальный SP, который выполняет поиск по CustomerID или по фамилии / имени.

0 голосов
/ 15 октября 2008

Моей первой мыслью было написать запрос наподобие этого ...

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
       )

... который затем заставил бы вызывающего абонента предоставить битовый флаг, если он хочет найти конкретное поле, а затем предоставить значение, если он хочет его искать, но я не знаю, создает ли это небрежное ГДЕ или если я могу сойти с заявления CASE в предложении WHERE.

Как вы можете видеть, этот конкретный код написан на T-SQL, но я с удовольствием посмотрю на некоторый код PL-SQL / MySQL и соответствующим образом адаптирую его.

...