Bagaimana saya bisa menggunakan parameter opsional dalam prosedur tersimpan T-SQL?

185

Saya membuat prosedur tersimpan untuk melakukan pencarian melalui tabel. Saya memiliki banyak bidang pencarian yang berbeda, semuanya opsional. Apakah ada cara untuk membuat prosedur tersimpan yang akan menangani ini? Katakanlah saya memiliki tabel dengan empat bidang: ID, FirstName, LastName, dan Judul. Saya bisa melakukan sesuatu seperti ini:

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

Jenis pekerjaan ini. Namun itu mengabaikan catatan di mana Nama depan, Nama belakang atau Judul NULL. Jika Judul tidak ditentukan dalam parameter pencarian saya ingin memasukkan catatan di mana Judul NULL - sama untuk FirstName dan LastName. Saya tahu saya mungkin bisa melakukan ini dengan SQL dinamis tetapi saya ingin menghindarinya.

Corey Burnett
sumber
Lihat di sini: stackoverflow.com/questions/11396919/…
Mario Eis
2
Coba ikuti pernyataan di mana: codeISNULL (FirstName, ') = ISNULL (@FirstName,' ') - ini akan membuat setiap NULL menjadi string kosong dan yang dapat dibandingkan melalui persamaan. operator. Jika Anda ingin mendapatkan semua judul jika parameter input nol, maka coba sesuatu seperti itu: codeFirstName = @FirstName ATAU @FirstName IS NULL.
baHI

Jawaban:

257

Mengubah pencarian secara dinamis berdasarkan parameter yang diberikan adalah subjek yang rumit dan melakukannya satu arah di atas yang lain, bahkan dengan perbedaan yang sangat kecil, dapat memiliki implikasi kinerja yang sangat besar. Kuncinya adalah menggunakan indeks, abaikan kode ringkas, abaikan khawatir tentang pengulangan kode, Anda harus membuat rencana eksekusi kueri yang baik (gunakan indeks).

Baca ini dan pertimbangkan semua metode. Metode terbaik Anda akan tergantung pada parameter Anda, data Anda, skema Anda, dan penggunaan Anda yang sebenarnya:

Kondisi Pencarian Dinamis dalam T-SQL oleh oleh Erland Sommarskog

Kutukan dan Berkat dari Dynamic SQL oleh Erland Sommarskog

Jika Anda memiliki versi SQL Server 2008 yang tepat (SQL 2008 SP1 CU5 (10.0.2746) dan yang lebih baru), Anda bisa menggunakan sedikit trik ini untuk benar-benar menggunakan indeks:

Tambahkan OPTION (RECOMPILE)ke kueri Anda, lihat artikel Erland , dan SQL Server akan menyelesaikan ORdari dalam (@LastName IS NULL OR LastName= @LastName)sebelum rencana kueri dibuat berdasarkan nilai runtime dari variabel lokal, dan indeks dapat digunakan.

Ini akan berfungsi untuk versi SQL Server (mengembalikan hasil yang tepat), tetapi hanya menyertakan OPSI (RECOMPILE) jika Anda menggunakan SQL 2008 SP1 CU5 (10.0.2746) dan yang lebih baru. OPSI (RECOMPILE) akan mengkompilasi ulang kueri Anda, hanya verison yang tercantum akan mengkompilasi ulang berdasarkan nilai waktu berjalan saat ini dari variabel lokal, yang akan memberi Anda kinerja terbaik. Jika tidak pada versi SQL Server 2008, biarkan saja garis itu.

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
KM.
sumber
15
Hati-hati dengan prioritas AND / OR. DAN memiliki prioritas di atas OR, jadi tanpa tanda kurung yang tepat, contoh ini tidak akan menghasilkan hasil yang diharapkan ... Jadi, ia harus membaca: (@FirstName IS NULL OR (FirstName = @FirstName)) AND (@LastNameIS NULL OR (LastName = @LastName)) AND (@TitleIS NULL OR (Title = @Title))
Bliek
... (@FirstName IS NULL ATAU (FirstName = @FirstName) harus ... (FirstName = Coalesce (@ firstname, FirstName))
fcm
Jangan lupa tanda kurung, jika tidak maka kurung tidak akan berhasil.
Pablo Carrasco Hernández
27

Jawaban dari @ KM sejauh ini baik tetapi gagal untuk sepenuhnya menindaklanjuti salah satu nasihat awalnya;

..., abaikan kode ringkas, abaikan khawatir tentang pengulangan kode, ...

Jika Anda mencari untuk mencapai kinerja terbaik maka Anda harus menulis permintaan khusus untuk setiap kemungkinan kombinasi kriteria opsional. Ini mungkin terdengar ekstrem, dan jika Anda memiliki banyak kriteria opsional maka mungkin saja, tetapi kinerja seringkali merupakan pertukaran antara upaya dan hasil. Dalam praktiknya, mungkin ada seperangkat kombinasi parameter umum yang dapat ditargetkan dengan kueri yang dipesan lebih dahulu, kemudian kueri umum (sesuai jawaban lain) untuk semua kombinasi lainnya.

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

Keuntungan dari pendekatan ini adalah bahwa dalam kasus-kasus umum yang ditangani oleh permintaan yang dipesan lebih dahulu permintaannya seefisien mungkin - tidak ada dampak dari kriteria yang tidak tersedia. Juga, indeks dan peningkatan kinerja lainnya dapat ditargetkan pada permintaan khusus yang dipesan khusus daripada mencoba untuk memenuhi semua situasi yang mungkin.

Rhys Jones
sumber
Tentunya akan lebih baik untuk menulis prosedur tersimpan yang terpisah untuk setiap kasus. Maka jangan khawatir tentang spoofing dan kompilasi ulang.
Jodrell
5
Harus dikatakan bahwa pendekatan ini dengan cepat menjadi mimpi buruk pemeliharaan.
Atario
3
@Atario Kemudahan pemeliharaan versus kinerja adalah trade off yang umum, jawaban ini diarahkan untuk kinerja.
Rhys Jones
26

Anda dapat melakukannya dalam kasus berikut,

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

Namun tergantung pada data kadang-kadang lebih baik membuat permintaan dinamis dan menjalankannya.

Michael Pakhantsov
sumber
10

Lima tahun terlambat ke pesta.

Disebutkan dalam tautan yang disediakan dari jawaban yang diterima, tetapi saya pikir itu layak mendapatkan jawaban eksplisit pada SO - secara dinamis membangun kueri berdasarkan parameter yang disediakan. Misalnya:

Mempersiapkan

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

Prosedur

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

Pemakaian

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

Pro:

  • mudah ditulis dan dimengerti
  • fleksibilitas - mudah menghasilkan permintaan untuk penyaringan yang lebih rumit (mis. TOP dinamis)

Cons:

  • kemungkinan masalah kinerja tergantung pada parameter yang disediakan, indeks dan volume data

Bukan jawaban langsung, tetapi terkait dengan masalah alias gambaran besarnya

Biasanya, prosedur tersimpan penyaringan ini tidak mengambang, tetapi dipanggil dari beberapa lapisan layanan. Ini meninggalkan pilihan untuk memindahkan logika bisnis (penyaringan) dari SQL ke lapisan layanan.

Salah satu contoh adalah menggunakan LINQ2SQL untuk menghasilkan kueri berdasarkan filter yang disediakan:

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

Pro:

  • kueri yang dihasilkan secara dinamis berdasarkan filter yang disediakan. Tidak ada parameter mengendus atau mengkompilasi ulang petunjuk
  • agak mudah untuk menulis untuk orang-orang di dunia OOP
  • biasanya ramah kinerja, karena permintaan "sederhana" akan dikeluarkan (indeks yang sesuai masih diperlukan)

Cons:

  • Keterbatasan LINQ2QL dapat dicapai dan memaksa downgrade ke LINQ2Objects atau kembali ke solusi SQL murni tergantung pada kasusnya
  • penulisan LINQ yang ceroboh dapat menghasilkan pertanyaan yang mengerikan (atau banyak pertanyaan, jika properti navigasi dimuat)
Alexei
sumber
1
Pastikan SEMUA string perantara Anda adalah N '' daripada '' - Anda akan mengalami masalah pemotongan jika SQL Anda melebihi 8000 karakter.
Alan Singfield
1
Juga, Anda mungkin perlu memasukkan klausa "WITH EXECUTE AS OWNER" ke prosedur yang tersimpan, jika Anda telah menolak izin SELECT langsung kepada pengguna. Hati-hati menghindari injeksi SQL jika Anda menggunakan klausa ini.
Alan Singfield
8

Perpanjang WHEREkondisi Anda :

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, '') = '')

yaitu menggabungkan berbagai kasus dengan kondisi boolean.

devio
sumber
-3

Ini juga berfungsi:

    ...
    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))
v2h
sumber