Як я можу використовувати необов'язкові параметри в T-SQL збереженій процедурі?


185

Я створюю збережену процедуру для пошуку через таблицю. У мене є багато різних полів пошуку, усі вони необов’язкові. Чи є спосіб створити збережену процедуру, яка б це впоралася? Скажімо, у мене є таблиця з чотирма полями: ID, FirstName, LastName та Title. Я міг би зробити щось подібне:

CREATE PROCEDURE spDoSearch
    @FirstName varchar(25) = null,
    @LastName varchar(25) = null,
    @Title varchar(25) = null
AS
    BEGIN
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
            FirstName = ISNULL(@FirstName, FirstName) AND
            LastName = ISNULL(@LastName, LastName) AND
            Title = ISNULL(@Title, Title)
    END

Цей вид робіт. Однак він ігнорує записи, де FirstName, LastName або Title є NULL. Якщо Title не вказано в параметрах пошуку, я хочу включити записи, де Title NULL - те саме для FirstName та LastName. Я знаю, що, ймовірно, я міг би це зробити за допомогою динамічного SQL, але хотів би цього уникнути.


Подивіться тут: stackoverflow.com/questions/11396919/…
Mario Eis

2
Спробуйте наступне: оператор: codeISNULL (FirstName, ') = ISNULL (@FirstName,' ') - це зробить кожен NULL порожнім рядком, і їх можна порівняти через eq. оператор. Якщо ви хочете отримати весь заголовок, якщо параметр введення недійсний, спробуйте щось подібне: codeFirstName = @FirstName АБО @FirstName NULL.
baHI

Відповіді:


257

Динамічно зміна пошуку на основі заданих параметрів є складною темою, і це робити один на інший, навіть лише з невеликою різницею, може мати величезні наслідки для продуктивності. Ключовим є використання індексу, ігнорування компактного коду, ігнорування переживань за повторення коду, ви повинні скласти хороший план виконання запитів (використовувати індекс).

Прочитайте це і врахуйте всі методи. Ваш найкращий метод буде залежати від ваших параметрів, ваших даних, вашої схеми та фактичного використання:

Динамічні умови пошуку в T-SQL Ерланда Соммарського

Прокляття та благословення динамічного SQL Ерланда Соммарського

Якщо у вас є відповідна версія SQL Server 2008 (SQL 2008 SP1 CU5 (10.0.2746) і пізнішої версії), ви можете використовувати цей маленький трюк, щоб фактично використовувати індекс:

Додайте OPTION (RECOMPILE)свій запит, див. Статтю Ерланда , і SQL Server вирішить ORзсередини, (@LastName IS NULL OR LastName= @LastName)перш ніж буде створений план запитів на основі значень часу виконання локальних змінних, і може бути використаний індекс.

Це працюватиме для будь-якої версії SQL Server (повертайте належні результати), але включайте ОПЦІЮ (РЕКОМПЛІЮ), якщо ви користуєтесь SQL 2008 SP1 CU5 (10.0.2746) та пізнішої версії. ВАРІАНТ (RECOMPILE) перекомпілює ваш запит, лише перерахований версіон перекомпілює його на основі поточних значень часу локальних змінних, що дасть вам найкращу ефективність. Якщо немає у цій версії SQL Server 2008, просто залиште цю лінію вимкнено.

CREATE PROCEDURE spDoSearch
    @FirstName varchar(25) = null,
    @LastName varchar(25) = null,
    @Title varchar(25) = null
AS
    BEGIN
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
                (@FirstName IS NULL OR (FirstName = @FirstName))
            AND (@LastName  IS NULL OR (LastName  = @LastName ))
            AND (@Title     IS NULL OR (Title     = @Title    ))
        OPTION (RECOMPILE) ---<<<<use if on for SQL 2008 SP1 CU5 (10.0.2746) and later
    END

15
Будьте обережні з пріоритетом І / АБО. І має перевагу над АБО, тому без належних дужок цей приклад не дасть очікуваних результатів ... Отже, він повинен прочитати: (@FirstName НУЛЬНИЙ АБО (FirstName = @FirstName)) AND (@LastNameIS NULL OR (LastName = @LastName)) AND (@TitleIS NULL OR (Назва = @Title))
Bliek

... (@FirstName IS NULL OR (FirstName = @FirstName) повинен бути ... (FirstName = Coalesce (@ firstname, FirstName))
fcm

Не забувайте дужки, інакше це не спрацює.
Пабло Карраско Ернандес

27

Відповідь від @KM є гарною, наскільки це стосується, але не вдається повністю дотримуватися одного з його ранніх порад;

..., ігноруйте компактний код, ігноруйте турбуючись про повторення коду, ...

Якщо ви хочете досягти найкращої продуктивності, тоді вам слід написати запит на замовлення для кожної можливої ​​комбінації додаткових критеріїв. Це може здатися екстремальним, і якщо у вас є багато необов'язкових критеріїв, то це може бути, але ефективність часто є компромісом між зусиллями та результатами. На практиці може існувати загальний набір комбінацій параметрів, який можна націлити за допомогою запитів, а також загальний запит (за іншими відповідями) для всіх інших комбінацій.

CREATE PROCEDURE spDoSearch
    @FirstName varchar(25) = null,
    @LastName varchar(25) = null,
    @Title varchar(25) = null
AS
BEGIN

    IF (@FirstName IS NOT NULL AND @LastName IS NULL AND @Title IS NULL)
        -- Search by first name only
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
            FirstName = @FirstName

    ELSE IF (@FirstName IS NULL AND @LastName IS NOT NULL AND @Title IS NULL)
        -- Search by last name only
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
            LastName = @LastName

    ELSE IF (@FirstName IS NULL AND @LastName IS NULL AND @Title IS NOT NULL)
        -- Search by title only
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
            Title = @Title

    ELSE IF (@FirstName IS NOT NULL AND @LastName IS NOT NULL AND @Title IS NULL)
        -- Search by first and last name
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
            FirstName = @FirstName
            AND LastName = @LastName

    ELSE
        -- Search by any other combination
        SELECT ID, FirstName, LastName, Title
        FROM tblUsers
        WHERE
                (@FirstName IS NULL OR (FirstName = @FirstName))
            AND (@LastName  IS NULL OR (LastName  = @LastName ))
            AND (@Title     IS NULL OR (Title     = @Title    ))

END

Перевага такого підходу полягає в тому, що в загальних випадках, які обробляються за замовчуванням запитів, запит є настільки ж ефективним, наскільки це може бути - вплив, який не застосовується, не застосовується. Крім того, індекси та інші підвищення ефективності можуть бути орієнтовані на конкретні запити, а не намагатися задовольнити всі можливі ситуації.


Звичайно, було б краще написати окрему збережену процедуру для кожного випадку. Тоді не турбуйтеся про підробку та перекомпіляцію.
Джодрелл

5
Само собою зрозуміло, що такий підхід швидко стає кошмаром технічного обслуговування.
Атаріо

3
@Atario Простота обслуговування в порівнянні з продуктивністю - це звичайна компроміс, ця відповідь орієнтована на продуктивність.
Різ Джонс

26

Можна зробити в наступному випадку,

CREATE PROCEDURE spDoSearch
   @FirstName varchar(25) = null,
   @LastName varchar(25) = null,
   @Title varchar(25) = null
AS
  BEGIN
      SELECT ID, FirstName, LastName, Title
      FROM tblUsers
      WHERE
        (@FirstName IS NULL OR FirstName = @FirstName) AND
        (@LastNameName IS NULL OR LastName = @LastName) AND
        (@Title IS NULL OR Title = @Title)
END

однак залежно від даних іноді краще створювати динамічний запит та виконувати їх.


10

П’ять років спізнюється до партії.

Він згадується в наданих посиланнях прийнятої відповіді, але я думаю, що він заслуговує на явну відповідь на SO - динамічно будуючи запит на основі наданих параметрів. Наприклад:

Налаштування

-- drop table Person
create table Person
(
    PersonId INT NOT NULL IDENTITY(1, 1) CONSTRAINT PK_Person PRIMARY KEY,
    FirstName NVARCHAR(64) NOT NULL,
    LastName NVARCHAR(64) NOT NULL,
    Title NVARCHAR(64) NULL
)
GO

INSERT INTO Person (FirstName, LastName, Title)
VALUES ('Dick', 'Ormsby', 'Mr'), ('Serena', 'Kroeger', 'Ms'), 
    ('Marina', 'Losoya', 'Mrs'), ('Shakita', 'Grate', 'Ms'), 
    ('Bethann', 'Zellner', 'Ms'), ('Dexter', 'Shaw', 'Mr'),
    ('Zona', 'Halligan', 'Ms'), ('Fiona', 'Cassity', 'Ms'),
    ('Sherron', 'Janowski', 'Ms'), ('Melinda', 'Cormier', 'Ms')
GO

Порядок

ALTER PROCEDURE spDoSearch
    @FirstName varchar(64) = null,
    @LastName varchar(64) = null,
    @Title varchar(64) = null,
    @TopCount INT = 100
AS
BEGIN
    DECLARE @SQL NVARCHAR(4000) = '
        SELECT TOP ' + CAST(@TopCount AS VARCHAR) + ' *
        FROM Person
        WHERE 1 = 1'

    PRINT @SQL

    IF (@FirstName IS NOT NULL) SET @SQL = @SQL + ' AND FirstName = @FirstName'
    IF (@LastName IS NOT NULL) SET @SQL = @SQL + ' AND FirstName = @LastName'
    IF (@Title IS NOT NULL) SET @SQL = @SQL + ' AND Title = @Title'

    EXEC sp_executesql @SQL, N'@TopCount INT, @FirstName varchar(25), @LastName varchar(25), @Title varchar(64)', 
         @TopCount, @FirstName, @LastName, @Title
END
GO

Використання

exec spDoSearch @TopCount = 3
exec spDoSearch @FirstName = 'Dick'

Плюси:

  • легко написати і зрозуміти
  • гнучкість - легко генерувати запит на складніші фільтрації (наприклад, динамічний TOP)

Мінуси:

  • можливі проблеми продуктивності залежно від наданих параметрів, індексів та обсягу даних

Не пряма відповідь, але пов'язана з проблемою, якою є велика картина

Зазвичай ці фільтруючі збережені процедури не плавають навколо, а викликаються з якогось сервісного рівня. Це залишає можливість переміщення бізнес-логіки (фільтрування) від SQL до сервісного рівня.

Одним із прикладів є використання LINQ2SQL для генерування запиту на основі наданих фільтрів:

    public IList<SomeServiceModel> GetServiceModels(CustomFilter filters)
    {
        var query = DataAccess.SomeRepository.AllNoTracking;

        // partial and insensitive search 
        if (!string.IsNullOrWhiteSpace(filters.SomeName))
            query = query.Where(item => item.SomeName.IndexOf(filters.SomeName, StringComparison.OrdinalIgnoreCase) != -1);
        // filter by multiple selection
        if ((filters.CreatedByList?.Count ?? 0) > 0)
            query = query.Where(item => filters.CreatedByList.Contains(item.CreatedById));
        if (filters.EnabledOnly)
            query = query.Where(item => item.IsEnabled);

        var modelList = query.ToList();
        var serviceModelList = MappingService.MapEx<SomeDataModel, SomeServiceModel>(modelList);
        return serviceModelList;
    }

Плюси:

  • динамічно генерований запит на основі наданих фільтрів. Немає параметри нюхання або перекомпіляції потрібні підказки
  • дещо простіше писати для тих, хто в світі OOP
  • як правило, ефективні, оскільки "простими" запитами будуть видаватися (хоча відповідні індекси все ще потрібні)

Мінуси:

  • Обмеження LINQ2QL можуть бути досягнуті і примусити понизити число до LINQ2Objects або повернутися до чистого рішення SQL залежно від випадку
  • необережне написання LINQ може генерувати жахливі запити (або багато запитів, якщо навігаційні властивості завантажені)

1
Переконайтеся, що ВСІ ваші проміжні рядки є N '', а не '' - у вас виникнуть проблеми з усіченням, якщо ваш SQL перевищує 8000 символів.
Алан Сінгфілд

1
Крім того, вам може знадобитися поставити на збережену процедуру пункт "З ВИКОНАННЯМ ЯК ВЛАСНИК", якщо ви відмовили користувачу в прямому дозволі SELECT. Будьте дуже обережні, щоб уникнути ін'єкції SQL, якщо ви використовуєте цей пункт.
Алан Сінгфілд

8

Подовжте свій WHEREстан:

WHERE
    (FirstName = ISNULL(@FirstName, FirstName)
    OR COALESCE(@FirstName, FirstName, '') = '')
AND (LastName = ISNULL(@LastName, LastName)
    OR COALESCE(@LastName, LastName, '') = '')
AND (Title = ISNULL(@Title, Title)
    OR COALESCE(@Title, Title, '') = '')

тобто поєднувати різні випадки з булевими умовами.


-3

Це також працює:

    ...
    WHERE
        (FirstName IS NULL OR FirstName = ISNULL(@FirstName, FirstName)) AND
        (LastName IS NULL OR LastName = ISNULL(@LastName, LastName)) AND
        (Title IS NULL OR Title = ISNULL(@Title, Title))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.