Làm cách nào tôi có thể sử dụng các tham số tùy chọn trong quy trình lưu trữ T-SQL?


184

Tôi đang tạo một thủ tục được lưu trữ để thực hiện tìm kiếm thông qua một bảng. Tôi có nhiều lĩnh vực tìm kiếm khác nhau, tất cả đều là tùy chọn. Có cách nào để tạo một thủ tục lưu trữ sẽ xử lý việc này không? Giả sử tôi có một bảng có bốn trường: ID, FirstName, LastName và Title. Tôi có thể làm một cái gì đó như thế này:

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

Đây là loại công trình. Tuy nhiên, nó bỏ qua các bản ghi trong đó FirstName, LastName hoặc Title là NULL. Nếu Tiêu đề không được chỉ định trong các tham số tìm kiếm, tôi muốn bao gồm các bản ghi trong đó Tiêu đề là NULL - tương tự cho FirstName và LastName. Tôi biết tôi có thể làm điều này với SQL động nhưng tôi muốn tránh điều đó.



2
Hãy thử làm theo câu lệnh where: codeISNULL (FirstName, ') = ISNULL (@FirstName,' ') - điều này sẽ làm cho mọi NULL thành một chuỗi trống và chúng có thể được so sánh qua eq. nhà điều hành. Nếu bạn muốn nhận tất cả tiêu đề nếu tham số đầu vào là null, thì hãy thử một cái gì đó như thế: codeFirstName = @FirstName HOẶC @FirstName IS NULL.
baHI

Câu trả lời:


257

Thay đổi động các tìm kiếm dựa trên các tham số đã cho là một chủ đề phức tạp và thực hiện theo cách này so với một tham số khác, thậm chí chỉ với một sự khác biệt rất nhỏ, có thể có ý nghĩa hiệu suất lớn. Điều quan trọng là sử dụng một chỉ mục, bỏ qua mã nhỏ gọn, bỏ qua việc lo lắng về việc lặp lại mã, bạn phải thực hiện một kế hoạch thực hiện truy vấn tốt (sử dụng một chỉ mục).

Đọc này và xem xét tất cả các phương pháp. Phương pháp tốt nhất của bạn sẽ phụ thuộc vào các tham số, dữ liệu, lược đồ và cách sử dụng thực tế của bạn:

Điều kiện tìm kiếm động trong T-SQL của Erland Sommarskog

Lời nguyền và phước lành của SQL động của Erland Sommarskog

Nếu bạn có phiên bản SQL Server 2008 phù hợp (SQL 2008 SP1 CU5 (10.0.2746) trở lên), bạn có thể sử dụng mẹo nhỏ này để thực sự sử dụng một chỉ mục:

Thêm OPTION (RECOMPILE)vào truy vấn của bạn, xem bài viết của Erland và SQL Server sẽ giải quyếtOR từ bên trong (@LastName IS NULL OR LastName= @LastName)trước khi kế hoạch truy vấn được tạo dựa trên các giá trị thời gian chạy của các biến cục bộ và có thể sử dụng một chỉ mục.

Điều này sẽ hoạt động cho bất kỳ phiên bản SQL Server nào (trả về kết quả phù hợp), nhưng chỉ bao gồm TÙY CHỌN (RECOMPILE) nếu bạn đang sử dụng SQL 2008 SP1 CU5 (10.0.2746) trở lên. TÙY CHỌN (RECOMPILE) sẽ biên dịch lại truy vấn của bạn, chỉ có verison được liệt kê sẽ biên dịch lại dựa trên các giá trị thời gian chạy hiện tại của các biến cục bộ, sẽ mang lại cho bạn hiệu suất tốt nhất. Nếu không có phiên bản SQL Server 2008 đó, hãy bỏ dòng đó đi.

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
Hãy cẩn thận với ưu tiên AND / OR. AND được ưu tiên hơn OR, vì vậy nếu không có dấu ngoặc thích hợp, ví dụ này sẽ không tạo ra kết quả như mong đợi ... Vì vậy, nó sẽ đọc: (@FirstName IS NULL OR (FirstName = @FirstName)) AND (@LastNameIS NULL OR (LastName = @LastName)) VÀ (@TitleIS NULL HOẶC (Tiêu đề = @Title))
Bliek

... (@FirstName LÀ NULL HOẶC (FirstName = @FirstName) phải là ... (FirstName = Coalesce (@ Firstname, FirstName))
fcm

Đừng quên dấu ngoặc đơn, nếu không nó sẽ không hoạt động.
Pablo Carrasco Hernández

27

Câu trả lời từ @KM là tốt cho đến nay nhưng không hoàn toàn theo dõi một trong những lời khuyên ban đầu của anh ấy;

..., bỏ qua mã nhỏ gọn, bỏ qua lo lắng về việc lặp lại mã, ...

Nếu bạn đang muốn đạt được hiệu suất tốt nhất thì bạn nên viết một truy vấn riêng cho từng kết hợp có thể của các tiêu chí tùy chọn. Điều này nghe có vẻ cực đoan và nếu bạn có nhiều tiêu chí tùy chọn thì có thể, nhưng hiệu suất thường là sự đánh đổi giữa nỗ lực và kết quả. Trong thực tế, có thể có một tập hợp các kết hợp tham số chung có thể được nhắm mục tiêu với các truy vấn bespoke, sau đó là một truy vấn chung (theo các câu trả lời khác) cho tất cả các kết hợp khác.

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

Ưu điểm của phương pháp này là trong các trường hợp phổ biến được xử lý bởi các truy vấn bespoke, truy vấn có hiệu quả nhất có thể - không có tác động bởi các tiêu chí không được áp dụng. Ngoài ra, các chỉ mục và các cải tiến hiệu suất khác có thể được nhắm mục tiêu vào các truy vấn bespoke cụ thể thay vì cố gắng đáp ứng tất cả các tình huống có thể.


Chắc chắn sẽ tốt hơn nếu viết một thủ tục lưu trữ riêng cho từng trường hợp. Sau đó, không phải lo lắng về giả mạo và biên dịch lại.
Jodrell

5
Nó nên đi mà không nói rằng phương pháp này nhanh chóng trở thành một cơn ác mộng bảo trì.
Atario

3
@Atario Dễ bảo trì so với hiệu suất là một sự đánh đổi phổ biến, câu trả lời này hướng đến hiệu suất.
Rhys Jones

26

Bạn có thể làm trong trường hợp sau đây,

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

tuy nhiên phụ thuộc vào dữ liệu đôi khi tốt hơn tạo truy vấn động và thực hiện chúng.


10

Năm năm trễ đến bữa tiệc.

Nó được đề cập trong các liên kết được cung cấp của câu trả lời được chấp nhận, nhưng tôi nghĩ rằng nó xứng đáng có câu trả lời rõ ràng về SO - tự động xây dựng truy vấn dựa trên các tham số được cung cấp. Ví dụ:

Thiết lập

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

Thủ tục

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

Sử dụng

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

Ưu điểm:

  • dễ viết và dễ hiểu
  • tính linh hoạt - dễ dàng tạo truy vấn cho các bộ lọc phức tạp hơn (ví dụ TOP động)

Nhược điểm:

  • vấn đề hiệu suất có thể phụ thuộc vào các tham số, chỉ mục và khối lượng dữ liệu được cung cấp

Không trả lời trực tiếp, nhưng liên quan đến vấn đề aka bức tranh lớn

Thông thường, các quy trình lọc được lưu trữ này không trôi nổi xung quanh, nhưng được gọi từ một số lớp dịch vụ. Điều này để lại tùy chọn di chuyển logic nghiệp vụ (lọc) từ lớp SQL sang lớp dịch vụ.

Một ví dụ là sử dụng LINQ2SQL để tạo truy vấn dựa trên các bộ lọc được cung cấp:

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

Ưu điểm:

  • truy vấn được tạo động dựa trên các bộ lọc được cung cấp. Không có thông số đánh hơi hoặc biên dịch lại gợi ý cần thiết
  • dễ viết hơn cho những người trong thế giới OOP
  • thông thường hiệu suất thân thiện, vì các truy vấn "đơn giản" sẽ được ban hành (mặc dù vẫn cần các chỉ mục thích hợp)

Nhược điểm:

  • Các giới hạn LINQ2QL có thể đạt được và buộc hạ cấp xuống LINQ2Objects hoặc quay trở lại giải pháp SQL thuần túy tùy theo trường hợp
  • Việc viết LINQ bất cẩn có thể tạo ra các truy vấn khủng khiếp (hoặc nhiều truy vấn, nếu thuộc tính điều hướng được tải)

1
Đảm bảo TẤT CẢ các chuỗi trung gian của bạn là N '' thay vì '' - bạn sẽ gặp phải các vấn đề cắt ngắn nếu SQL của bạn vượt quá 8000 ký tự.
Alan Singfield

1
Ngoài ra, bạn có thể cần đặt mệnh đề "VỚI EXECUTE AS OWNER" vào quy trình được lưu trữ, nếu bạn đã từ chối quyền CHỌN trực tiếp cho người dùng. Hãy thực sự cẩn thận để tránh SQL tiêm nếu bạn sử dụng điều khoản này mặc dù.
Alan Singfield

8

Mở rộng WHEREđiều kiện của bạn :

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

tức là kết hợp các trường hợp khác nhau với các điều kiện boolean.


-3

Điều này cũng hoạt động:

    ...
    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))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.