Wie kann ich optionale Parameter in einer gespeicherten T-SQL-Prozedur verwenden?

184

Ich erstelle eine gespeicherte Prozedur, um eine Tabelle zu durchsuchen. Ich habe viele verschiedene Suchfelder, die alle optional sind. Gibt es eine Möglichkeit, eine gespeicherte Prozedur zu erstellen, die dies handhabt? Angenommen, ich habe eine Tabelle mit vier Feldern: ID, Vorname, Nachname und Titel. Ich könnte so etwas machen:

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

Diese Art von Arbeiten. Datensätze, bei denen Vorname, Nachname oder Titel NULL sind, werden jedoch ignoriert. Wenn Titel nicht in den Suchparametern angegeben ist, möchte ich Datensätze einschließen, bei denen Titel NULL ist - dasselbe gilt für Vorname und Nachname. Ich weiß, dass ich dies wahrscheinlich mit dynamischem SQL tun könnte, aber ich möchte das vermeiden.

Corey Burnett
quelle
Schauen Sie hier: stackoverflow.com/questions/11396919/…
Mario Eis
2
Versuchen Sie, der where-Anweisung zu codefolgen : ISNULL (FirstName, ') = ISNULL (@FirstName,' ') - dies macht jede NULL zu einer leeren Zeichenfolge und diese können über Gl. Operator. Wenn Sie alle Titel erhalten möchten, wenn der Eingabeparameter null ist, versuchen codeSie Folgendes : Vorname = @FirstName ODER @FirstName IST NULL.
BaHI

Antworten:

257

Das dynamische Ändern von Suchvorgängen basierend auf den angegebenen Parametern ist ein kompliziertes Thema, und es kann, selbst wenn nur ein sehr geringer Unterschied besteht, massive Auswirkungen auf die Leistung haben. Der Schlüssel besteht darin, einen Index zu verwenden, kompakten Code zu ignorieren und sich keine Gedanken über das Wiederholen von Code zu machen. Sie müssen einen guten Plan für die Ausführung von Abfragen erstellen (einen Index verwenden).

Lesen Sie dies und berücksichtigen Sie alle Methoden. Ihre beste Methode hängt von Ihren Parametern, Ihren Daten, Ihrem Schema und Ihrer tatsächlichen Verwendung ab:

Dynamische Suchbedingungen in T-SQL von Erland Sommarskog

Der Fluch und der Segen von Dynamic SQL von Erland Sommarskog

Wenn Sie über die richtige SQL Server 2008-Version (SQL 2008 SP1 CU5 (10.0.2746) und höher) verfügen, können Sie mit diesem kleinen Trick tatsächlich einen Index verwenden:

Fügen Sie OPTION (RECOMPILE)Ihrer Abfrage hinzu, siehe Erlands Artikel , und SQL Server löst das Problem ORvon innen auf, (@LastName IS NULL OR LastName= @LastName)bevor der Abfrageplan basierend auf den Laufzeitwerten der lokalen Variablen erstellt wird, und es kann ein Index verwendet werden.

Dies funktioniert für jede SQL Server-Version (gibt die richtigen Ergebnisse zurück), schließt jedoch nur die OPTION (RECOMPILE) ein, wenn Sie SQL 2008 SP1 CU5 (10.0.2746) und höher verwenden. Die OPTION (RECOMPILE) kompiliert Ihre Abfrage neu, nur die aufgelistete Version kompiliert sie basierend auf den aktuellen Laufzeitwerten der lokalen Variablen neu, wodurch Sie die beste Leistung erzielen. Wenn nicht in dieser Version von SQL Server 2008, lassen Sie diese Zeile einfach weg.

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.
quelle
15
Seien Sie vorsichtig mit der UND / ODER-Priorität. AND hat Vorrang vor OR, daher führt dieses Beispiel ohne die richtigen Klammern nicht zu den erwarteten Ergebnissen ... Es sollte also lauten: (@FirstName IS NULL OR (Vorname = @FirstName)) AND (@LastNameIS NULL OR (Nachname =) @LastName)) AND (@TitleIS NULL OR (Title = @Title))
Bliek
... (@FirstName IST NULL ODER (FirstName = @FirstName) sollte sein ... (FirstName = Coalesce (@ Vorname, Vorname))
fcm
Vergessen Sie nicht die Klammern, sonst funktioniert es nicht.
Pablo Carrasco Hernández
27

Die Antwort von @KM ist soweit gut, geht aber einem seiner frühen Ratschläge nicht vollständig nach.

..., ignorieren Sie kompakten Code, ignorieren Sie die Sorge, Code zu wiederholen, ...

Wenn Sie die beste Leistung erzielen möchten, sollten Sie für jede mögliche Kombination optionaler Kriterien eine maßgeschneiderte Abfrage schreiben. Dies mag extrem klingen, und wenn Sie viele optionale Kriterien haben, ist dies möglicherweise der Fall, aber die Leistung ist häufig ein Kompromiss zwischen Aufwand und Ergebnissen. In der Praxis gibt es möglicherweise einen gemeinsamen Satz von Parameterkombinationen, auf die mit maßgeschneiderten Abfragen abgezielt werden kann, und anschließend eine generische Abfrage (gemäß den anderen Antworten) für alle anderen Kombinationen.

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

Der Vorteil dieses Ansatzes besteht darin, dass in den allgemeinen Fällen, in denen maßgeschneiderte Abfragen durchgeführt werden, die Abfrage so effizient wie möglich ist - die nicht bereitgestellten Kriterien haben keine Auswirkungen. Außerdem können Indizes und andere Leistungsverbesserungen auf bestimmte maßgeschneiderte Abfragen ausgerichtet werden, anstatt zu versuchen, alle möglichen Situationen zu erfüllen.

Rhys Jones
quelle
Sicherlich wäre es besser, für jeden Fall eine separate gespeicherte Prozedur zu schreiben. Dann mach dir keine Sorgen um Spoofing und Neukompilierung.
Jodrell
5
Es versteht sich von selbst, dass dieser Ansatz schnell zu einem Alptraum für die Instandhaltung wird.
Atario
3
@Atario Einfache Wartung im Vergleich zur Leistung ist ein häufiger Kompromiss. Diese Antwort ist auf die Leistung ausgerichtet.
Rhys Jones
26

Sie können dies im folgenden Fall tun:

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

Abhängig von Daten ist es jedoch manchmal besser, dynamische Abfragen zu erstellen und auszuführen.

Michael Pakhantsov
quelle
10

Fünf Jahre zu spät zur Party.

Es wird in den bereitgestellten Links der akzeptierten Antwort erwähnt, aber ich denke, es verdient eine explizite Antwort auf SO - dynamisches Erstellen der Abfrage basierend auf den bereitgestellten Parametern. Z.B:

Konfiguration

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

Verfahren

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

Verwendung

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

Vorteile:

  • leicht zu schreiben und zu verstehen
  • Flexibilität - Generieren Sie einfach die Abfrage für schwierigere Filter (z. B. dynamisches TOP).

Nachteile:

  • Mögliche Leistungsprobleme in Abhängigkeit von den angegebenen Parametern, Indizes und dem Datenvolumen

Keine direkte Antwort, sondern im Zusammenhang mit dem Problem, auch bekannt als das große Ganze

Normalerweise schweben diese gespeicherten Filterprozeduren nicht herum, sondern werden von einer Service-Schicht aufgerufen. Dies lässt die Möglichkeit, die Geschäftslogik (Filterung) von SQL auf die Serviceschicht zu verlagern.

Ein Beispiel ist die Verwendung von LINQ2SQL, um die Abfrage basierend auf den bereitgestellten Filtern zu generieren:

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

Vorteile:

  • dynamisch generierte Abfrage basierend auf bereitgestellten Filtern. Es sind keine Parameter-Sniffing- oder Neukompilierungshinweise erforderlich
  • etwas einfacher für diejenigen in der OOP-Welt zu schreiben
  • In der Regel leistungsfreundlich, da "einfache" Abfragen ausgegeben werden (entsprechende Indizes werden jedoch weiterhin benötigt).

Nachteile:

  • Je nach Fall können LINQ2QL-Einschränkungen erreicht werden, die ein Downgrade auf LINQ2Objects oder die Rückkehr zu einer reinen SQL-Lösung erzwingen
  • Unachtsames Schreiben von LINQ kann zu schrecklichen Abfragen führen (oder zu vielen Abfragen, wenn Navigationseigenschaften geladen werden).
Alexei
quelle
1
Stellen Sie sicher, dass ALLE Ihre Zwischenzeichenfolgen N '' statt '' sind. Wenn Ihre SQL 8000 Zeichen überschreitet, treten Kürzungsprobleme auf.
Alan Singfield
1
Außerdem müssen Sie möglicherweise eine Klausel "WITH EXECUTE AS OWNER" in die gespeicherte Prozedur einfügen, wenn Sie dem Benutzer die direkte SELECT-Berechtigung verweigert haben. Vermeiden Sie SQL-Injection, wenn Sie diese Klausel verwenden.
Alan Singfield
8

Erweitern Sie Ihren WHEREZustand:

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

dh verschiedene Fälle mit booleschen Bedingungen kombinieren.

devio
quelle
-3

Das funktioniert auch:

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