Как мне создать хранимую процедуру, которая при необходимости будет искать столбцы?

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

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

Я хотел упомянуть здесь, что решение Cade Roux сработало для меня лучше всего, так как у меня было много значений данных NULL в целевой таблице, но я мог видеть, насколько хорошо COALESCE будет работать, если у меня будут данные во всех моих столбцах, поэтому голосование за Решение BoltBait определенно заслуживает внимания.

Dillie-O 16.10.2008 01:06

Аарон Бертран называет это «процедурой кухонной раковины» и имеет несколько хороших мыслей о том, как справиться с этим типом проблем, которые можно увидеть в sqlsentry.tv/the-kitchen-sink-procedure и blogs.sqlsentry.com/aaronbertrand/….

JamieSee 22.09.2016 00:21
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
9
2
29 159
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Первой моей мыслью было написать такой запрос ...

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 или если Я могу обойтись без оператора CASE в предложении WHERE.

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

Мне просто интересно, что не так с этим запросом, который получает отрицательные голоса?

Teoman shipahi 02.08.2013 23:25

@Teomanshipahi Проблема с моим запросом заключается в том, что ему требуются две переменные, бит для поиска в поле, а затем сам параметр, что приводит к привязке очень большой переменной хранимой процедуры. Используя coalesce, вы можете использовать одну переменную для каждого поля, которое хотите запросить. Думаю, мне следует удалить свой ответ, учитывая, сколько лет этому потоку 8 ^ D

Dillie-O 03.08.2013 00:44

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

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

Я не думаю, что это вообще хорошо работает. Вы получаете огромный процент сканирований таблиц, потому что эти предикаты не подлежат поиску по запросу.

Pittsburgh DBA 15.10.2008 21:03

Когда вы говорите, что значение установлено на NULL, вы имеете в виду поиск NULL в имени столбца или он игнорируется. Единственная проблема, которую я вижу в этом, заключается в том, что если я ищу людей с фамилией Шмо, то имя «Джо» будет исключено, поскольку значение не равно NULL.

Dillie-O 15.10.2008 21:03

Дилли-О, найдите команду COALESCE, чтобы узнать, почему это работает (или перейдите по ссылке, приведенной в моем сообщении). Что касается производительности такого рода вещей ... система, в которой я это реализовал, имела от 1 до 2 миллионов строк, и она работала нормально. Это совсем не казалось медленным. YMMV.

BoltBait 15.10.2008 21:13

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

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

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

В предыдущем ответе вы сказали, что это не так хорошо. Как вы думаете, я просто жму пулю, делаю много очистки ввода и создаю запрос AdHoc или создаю более специализированные процедуры для всех различных параметров?

Dillie-O 15.10.2008 21:08

Что ж, это одна из причин, по которой системы ORM (объектно-реляционная модель) очень сильно входят в моду. Большинство из них используют динамическую генерацию запросов, но используют параметризацию, чтобы у вас не возникало проблем с внедрением. Посмотрите на параметризованные запросы. В этом случае это могло бы послужить вам лучше.

Pittsburgh DBA 15.10.2008 21:15

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

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

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

Как это будет работать с моим полем идентификатора отдела? Могу ли я использовать "%" для int или указать другой синтаксис?

Dillie-O 15.10.2008 21:23

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

Cade Roux 15.10.2008 21:35

Чувак, это именно то, что я искал. Спасибо!

Electrons_Ahoy 08.11.2008 03:16

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

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

Я работал с приложениями CRM, где на экране поиска 30 полей. Там перестановки работают против вас. ORM сияют в этой области.

Pittsburgh DBA 16.10.2008 02:34

Я не предлагаю вам создавать разные SP для каждой перестановки. (Это будет 2 ^ 30 хранимых процедур). Согласны ли вы, что имеет смысл иметь специальный случайный SP в случае, если пользователь выполняет поиск по идентификатору клиента (который, как я полагаю, уникален)?

Aheho 16.10.2008 16:49

Опять же, я предлагаю это только в том случае, если он обнаружит, что метод COALESCE создает неоптимальные планы запросов, если присутствует CustomerID.

Aheho 16.10.2008 16:50

Проблема использования метода 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

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

Мы можем использовать 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

Другие вопросы по теме