ฉันจะใช้พารามิเตอร์ทางเลือกในขั้นตอนการจัดเก็บ 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 หากไม่ได้ระบุชื่อในพารามิเตอร์การค้นหาฉันต้องการรวมระเบียนที่ชื่อเป็น NULL เหมือนกันสำหรับชื่อและนามสกุล ฉันรู้ว่าฉันสามารถทำได้ด้วย SQL แบบไดนามิก แต่ฉันต้องการหลีกเลี่ยงสิ่งนั้น


ดูได้ที่นี่: stackoverflow.com/questions/11396919/…
Mario Eis

2
ลองทำตามคำสั่งที่: codeISNULL (FirstName, ') = ISNULL (@FirstName,' ') - สิ่งนี้จะทำให้ NULL ทุกตัวเป็นสตริงว่างและสามารถเปรียบเทียบได้ผ่าน eq ผู้ประกอบการ หากคุณต้องการได้รับหัวเรื่องทั้งหมดหากพารามิเตอร์อินพุตเป็นโมฆะลองทำดังนี้: codeFirstName = @FirstName หรือ @FirstName IS NULL
baHI

คำตอบ:


257

การค้นหาที่เปลี่ยนแปลงแบบไดนามิกโดยอิงตามพารามิเตอร์ที่กำหนดนั้นเป็นเรื่องที่ซับซ้อนและทำอย่างใดอย่างหนึ่งมากกว่าที่อื่นแม้จะมีความแตกต่างเพียงเล็กน้อยเท่านั้น กุญแจสำคัญคือการใช้ดัชนีละเว้นรหัสกะทัดรัดละเว้นการกังวลเกี่ยวกับรหัสซ้ำคุณต้องสร้างแผนการดำเนินการแบบสอบถามที่ดี (ใช้ดัชนี)

อ่านและพิจารณาวิธีการทั้งหมด วิธีที่ดีที่สุดของคุณจะขึ้นอยู่กับพารามิเตอร์ข้อมูลสคีมาและการใช้งานจริงของคุณ:

เงื่อนไขการค้นหาแบบไดนามิกใน T-SQL โดย Erland Sommarskog

คำสาปและพรของ Dynamic SQL โดย Erland Sommarskog

หากคุณมีรุ่นที่เหมาะสมของ SQL Server 2008 (SQL 2008 SP1 CU5 (10.0.2746) และใหม่กว่า) คุณสามารถใช้เคล็ดลับเล็กน้อยนี้เพื่อใช้ดัชนีจริง:

เพิ่มOPTION (RECOMPILE)ลงในคิวรีของคุณ ดูที่บทความของ Erlandและ SQL Server จะแก้ไขORจากภายใน(@LastName IS NULL OR LastName= @LastName)ก่อนที่จะสร้างแผนคิวรีตามค่ารันไทม์ของตัวแปรโลคัลและสามารถใช้ดัชนีได้

สิ่งนี้จะใช้ได้กับ SQL Server ทุกเวอร์ชัน (ให้ผลลัพธ์ที่เหมาะสม) แต่จะรวมเฉพาะ OPTION (RECOMPILE) หากคุณใช้ SQL 2008 SP1 CU5 (10.0.2746) และใหม่กว่า OPTION (RECOMPILE) จะคอมไพล์แบบสอบถามของคุณอีกครั้งเฉพาะ verison ที่ระบุไว้เท่านั้นที่จะคอมไพล์ใหม่ตามค่าเวลาทำงานปัจจุบันของตัวแปรโลคัลซึ่งจะให้ประสิทธิภาพที่ดีที่สุดแก่คุณ หากไม่ได้อยู่ใน 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
ระวังด้วยการใช้ AND / OR และมีความสำคัญมากกว่า OR ดังนั้นหากไม่มีวงเล็บเหลี่ยมที่เหมาะสมตัวอย่างนี้จะไม่สร้างผลลัพธ์ที่คาดหวัง ... ดังนั้นจึงควรอ่าน: (@FirstName IS NULL OR (FirstName = @FirstName)) และ (@LastNameIS NULL OR (นามสกุล = @LastName)) และ (@TitleIS NULL OR (Title = @Title))
Bliek

... (@FirstName is NULL OR (FirstName = @FirstName) ควรเป็น ... (FirstName = Coalesce (@ FirstName, FirstName))
fcm

อย่าลืมวงเล็บมิฉะนั้นมันจะไม่ทำงาน
Pablo Carrasco Hernández

27

คำตอบจาก @KM นั้นดีที่สุดเท่าที่จะทำได้ แต่ไม่สามารถติดตามคำแนะนำแรก ๆ ของเขาได้อย่างเต็มที่

... , ไม่สนใจรหัสกะทัดรัด, ไม่ต้องกังวลกับการทำซ้ำรหัส, ...

หากคุณกำลังมองหาเพื่อให้ได้ประสิทธิภาพที่ดีที่สุดคุณควรเขียนแบบสอบถามแบบ bespoke สำหรับแต่ละชุดที่เป็นไปได้ของเกณฑ์ที่เป็นทางเลือก สิ่งนี้อาจฟังดูสุดขีดและหากคุณมีเกณฑ์ที่เป็นตัวเลือกมากมายอาจเป็นไปได้ แต่ประสิทธิภาพมักจะเป็นการแลกเปลี่ยนระหว่างความพยายามและผลลัพธ์ ในทางปฏิบัติอาจมีชุดค่าผสมของพารามิเตอร์ทั่วไปที่สามารถกำหนดเป้าหมายด้วยแบบสอบถามแบบ bespoke จากนั้นแบบสอบถามทั่วไป (ตามคำตอบอื่น ๆ ) สำหรับชุดค่าผสมอื่น ๆ ทั้งหมด

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

ข้อดีของวิธีการนี้คือในกรณีทั่วไปที่จัดการโดยการสอบถาม bespoke การสืบค้นมีประสิทธิภาพมากที่สุดเท่าที่จะเป็นไปได้โดยไม่มีผลกระทบจากเกณฑ์ที่ไม่ได้ให้มา นอกจากนี้ดัชนีและการปรับปรุงประสิทธิภาพอื่น ๆ สามารถกำหนดเป้าหมายตามการสืบค้นที่เฉพาะเจาะจงแทนที่จะพยายามตอบสนองทุกสถานการณ์ที่เป็นไปได้


แน่นอนว่าการเขียนขั้นตอนการจัดเก็บแยกต่างหากสำหรับแต่ละกรณีย่อมดีกว่า จากนั้นไม่ต้องกังวลเกี่ยวกับการปลอมแปลงและการคอมไพล์ซ้ำ
Jodrell

5
มันควรจะไปโดยไม่บอกว่าวิธีนี้กลายเป็นฝันร้ายของการบำรุงรักษาอย่างรวดเร็ว
Atario

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)

จุดด้อย:

  • ปัญหาประสิทธิภาพที่เป็นไปได้ขึ้นอยู่กับพารามิเตอร์ดัชนีและปริมาณข้อมูลที่ให้

ไม่ใช่คำตอบโดยตรง แต่เกี่ยวข้องกับปัญหา aka ภาพรวมขนาดใหญ่

โดยปกติแล้วขั้นตอนการจัดเก็บตัวกรองเหล่านี้จะไม่ลอยไปมา แต่ถูกเรียกจากชั้นบริการบางแห่ง สิ่งนี้ทำให้ตัวเลือกในการย้ายตรรกะทางธุรกิจออกไป (กรอง) จาก 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 ตัว
Alan Singfield

1
นอกจากนี้คุณอาจต้องใส่คำสั่ง "With EXECUTE AS OWNER" ลงในกระบวนงานที่เก็บไว้หากคุณปฏิเสธการอนุญาต SELECT โดยตรงแก่ผู้ใช้ ระมัดระวังการหลีกเลี่ยงการฉีด SQL ถ้าคุณใช้ข้อนี้
Alan Singfield

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))
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.