T-SQL saklı yordamında isteğe bağlı parametreleri nasıl kullanabilirim?


185

Bir tablo üzerinden arama yapmak için saklı bir yordam oluşturuyorum. Hepsi isteğe bağlı birçok farklı arama alanım var. Bunu ele alacak saklı bir yordam oluşturmak için bir yolu var mı? Diyelim ki dört alanlı bir tablom var: ID, FirstName, LastName ve Title. Böyle bir şey yapabilirdim:

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

Bu tür işler. Ancak FirstName, LastName veya Title'ın NULL olduğu kayıtları yok sayar. Arama parametrelerinde Başlık belirtilmezse, Başlığın NULL olduğu kayıtları eklemek istiyorum - FirstName ve LastName için aynı. Bunu muhtemelen dinamik SQL ile yapabileceğimi biliyorum ama bundan kaçınmak istiyorum.



2
Burada aşağıdaki ifadeyi deneyin: codeISNULL (FirstName, ') = ISNULL (@FirstName,' ') - bu, her NULL değerini boş bir dizeye dönüştürür ve bunlar eq ile karşılaştırılabilir. Şebeke. İnput parametresi null ise tüm başlığı almak istiyorsanız, şöyle bir şey deneyin: codeFirstName = @FirstName VEYA @FirstName IS NULL.
baHI

Yanıtlar:


257

Verilen parametrelere dayalı olarak dinamik olarak değişen aramalar karmaşık bir konudur ve bunu sadece çok küçük bir farkla bile diğerine göre yapmak muazzam performans sonuçları olabilir. Anahtar, bir dizin kullanmak, kompakt kodu yok saymak, tekrar kodu hakkında endişe yoksay, iyi bir sorgu yürütme planı (bir dizin kullanın) yapmak gerekir.

Bunu okuyun ve tüm yöntemleri düşünün. En iyi yönteminiz parametrelerinize, verilerinize, şemanıza ve gerçek kullanımınıza bağlı olacaktır:

Erland Sommarskog'dan T-SQL'de Dinamik Arama Koşulları

Dinamik SQL'in Laneti ve Bereketleri Erland Sommarskog

Uygun SQL Server 2008 sürümüne (SQL 2008 SP1 CU5 (10.0.2746) ve sonraki sürümleri) sahipseniz, aslında bir dizin kullanmak için bu küçük numarayı kullanabilirsiniz:

OPTION (RECOMPILE)Sorgunuza ekleyin , Erland'ın makalesine bakın ; SQL Server , yerel değişkenlerin çalışma zamanı değerlerine dayalı olarak sorgu planı oluşturulmadan önce ORiçinden çözer (@LastName IS NULL OR LastName= @LastName)ve bir dizin kullanılabilir.

Bu, herhangi bir SQL Server sürümü (doğru sonuçları döndür) için çalışır, ancak yalnızca SQL 2008 SP1 CU5 (10.0.2746) ve sonraki bir sürümü kullanıyorsanız OPTION (RECOMPILE) öğesini içerir. OPTION (RECOMPILE) sorgunuzu yeniden derleyecek, yalnızca listelenen sürüm yerel değişkenlerin geçerli çalışma süresi değerlerine göre yeniden derleyecektir ve bu da size en iyi performansı verecektir. SQL Server 2008'in bu sürümünde değilse, bu satırı kapalı bırakın.

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
VE / VEYA önceliğine dikkat edin. AND, VEYA'ya göre önceliğe sahiptir, bu nedenle uygun parantez olmadan bu örnek beklenen sonuçları üretmez ... Bu nedenle şunu okumalıdır: (@FirstName NULL OR (FirstName = @FirstName)) AND (@LastNameIS NULL OR (LastName = @LastName)) VE (@TitleIS BOŞ VEYA (Başlık = When @ BAŞLıK))
Bliek

... (@FirstName NULL VEYA (FirstName = @FirstName) olmalı ... (FirstName = Coalesce (@ firstname, FirstName))
fcm

Parantezleri unutmayın, aksi takdirde çalışmaz.
Pablo Carrasco Hernández

27

@KM'nin cevabı gittikçe iyidir, ancak ilk tavsiye parçalarından birini tam olarak takip edemez;

..., kompakt kodu yoksay, tekrarlayan kod hakkında endişelenmeyi yoksay, ...

En iyi performansı elde etmek istiyorsanız, isteğe bağlı kriterlerin her olası kombinasyonu için ısmarlama bir sorgu yazmalısınız. Bu kulağa aşırı gelebilir ve çok sayıda isteğe bağlı kritere sahipseniz, bu olabilir, ancak performans genellikle çaba ve sonuçlar arasında bir değiş tokuştur. Uygulamada, ısmarlama sorgularla hedeflenebilecek ortak bir parametre kombinasyonları seti, ardından diğer tüm kombinasyonlar için genel bir sorgu (diğer cevaplara göre) olabilir.

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

Bu yaklaşımın avantajı, ısmarlama sorgularla ele alınan yaygın durumlarda, sorgunun olabildiğince verimli olmasıdır - tedarik edilmeyen kriterlerin hiçbir etkisi yoktur. Ayrıca, dizinler ve diğer performans geliştirmeleri olası tüm durumları karşılamaya çalışmak yerine özel ısmarlama sorguları hedefleyebilir.


Elbette her vaka için ayrı bir saklı yordam yazmak daha iyi olacaktır. Sonra kimlik sahtekarlığı ve yeniden derleme konusunda endişelenmeyin.
Jodrell

5
Bu yaklaşımın hızla bir bakım kabusu haline geldiğini söylemeye gerek yok.
Atario

3
@ Ontario Performansa karşı bakım kolaylığı yaygın bir değiş tokuş, bu cevap performansa yönelik.
Rhys Jones

26

Aşağıdaki durumda yapabilirsiniz,

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

ancak verilere bağlı olarak dinamik sorgu oluşturmak ve yürütmek bazen daha iyidir.


10

Partiye beş yıl geç kaldı.

Kabul edilen yanıtın sağlanan bağlantılarında belirtilmiştir, ancak bence SO - dinamik olarak sağlanan parametrelere dayalı olarak sorguyu oluşturma konusunda açık bir cevabı hak ediyor. Örneğin:

Kurmak

-- 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

prosedür

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

kullanım

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

Artıları:

  • yazması ve anlaması kolay
  • esneklik - daha zor filtreler için sorguyu kolayca oluşturun (ör. dinamik TOP)

Eksileri:

  • sağlanan parametrelere, dizinlere ve veri hacmine bağlı olarak olası performans sorunları

Doğrudan cevap değil, sorunla ilgili büyük resim

Genellikle, bu filtreleme saklı yordamları yüzer, ancak bazı hizmet katmanlarından çağrılır. Bu, iş mantığını (filtreleme) SQL'den hizmet katmanına taşıma seçeneğini bırakır.

Bir örnek, sağlanan filtrelere dayalı olarak sorgu oluşturmak için LINQ2SQL kullanmaktır:

    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;
    }

Artıları:

  • sağlanan filtrelere dayalı dinamik olarak oluşturulan sorgu. Hiçbir parametre koklama veya yeniden derleme ipuçları gerekli
  • OOP dünyasındaki insanlar için yazmak biraz daha kolay
  • "basit" sorgular yayınlanacağı için genellikle performans dostudur (yine de uygun dizinlere ihtiyaç vardır)

Eksileri:

  • LINQ2QL sınırlamalarına ulaşılabilir ve duruma bağlı olarak LINQ2Objects sürümüne geçmeye veya saf SQL çözümüne geri dönmeye zorlanabilir
  • LINQ'nun dikkatsizce yazılması korkunç sorgular (veya navigasyon özellikleri yüklüyse birçok sorgu) oluşturabilir

1
Ara dizelerinizin TÜM '' yerine N '' olduğundan emin olun - SQL'iniz 8000 karakteri aşıyorsa kesme sorunlarıyla karşılaşırsınız.
Alan Singfield

1
Ayrıca, kullanıcıya doğrudan SELECT iznini reddetmeniz durumunda, saklı yordamın üzerine "SAHİBİ OLDUĞU İLE" yan tümcesi koymanız gerekebilir. Bu maddeyi kullanırsanız SQL enjeksiyonundan kaçınmaya gerçekten dikkat edin.
Alan Singfield

8

Durumunuzu uzatın 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, '') = '')

yani farklı vakaları boole koşullarıyla birleştirmek.


-3

Bu ayrıca işe yarar:

    ...
    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))
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.