2010-08-05 6 views
147

Ich erstelle eine gespeicherte Prozedur, um eine Suche in einer Tabelle durchzuführen. Ich habe viele verschiedene Suchfelder, die alle optional sind. Gibt es eine Möglichkeit, eine gespeicherte Prozedur zu erstellen, die das behandelt? Nehmen wir an, ich habe eine Tabelle mit vier Feldern: ID, Vorname, Nachname und Titel. Ich könnte so etwas tun:Wie kann ich optionale Parameter in einer gespeicherten T-SQL-Prozedur verwenden?

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. Es ignoriert jedoch Datensätze, bei denen Vorname, Nachname oder Titel NULL sind. Wenn Titel nicht in den Suchparametern angegeben ist, möchte ich Datensätze einschließen, bei denen Titel NULL ist - gleich für Vorname und Nachname. Ich weiß, dass ich das wahrscheinlich mit dynamischem SQL machen könnte, aber das möchte ich vermeiden.

+0

Werfen Sie einen Blick hier: http://stackoverflow.com/questions/11396919/building-dynamic-where-clause -in-stored-procedure/25473624 # 25473624 –

+1

Versuchen Sie, folgende Anweisung zu folgen: 'code' ISNULL (Vorname, ') = ISNULL (@FirstName,' ') - dies wird jede NULL zu einer leeren Zeichenfolge und diese kann über Gl. Operator. Wenn Sie alle Titel erhalten möchten, wenn der Eingabeparameter null ist, versuchen Sie Folgendes: 'code'FirstName = @FirstName ODER @FirstName IS NULL. – baHI

Antwort

214

Dynamisch wechselnde Suchvorgänge basierend auf den angegebenen Parametern sind ein kompliziertes Thema, und selbst wenn nur ein sehr kleiner Unterschied besteht, kann es massive Auswirkungen auf die Leistung haben. Der Schlüssel besteht darin, einen Index zu verwenden, kompakten Code zu ignorieren, Bedenken über das Wiederholen von Code zu ignorieren, Sie müssen einen guten Abfrageausführungsplan erstellen (verwenden Sie einen Index).

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

Dynamic Search Conditions in T-SQL by by Erland Sommarskog

The Curse and Blessings of Dynamic SQL by Erland Sommarskog

Wenn Sie die richtigen SQL Server 2008-Version (SQL 2008 SP1 CU5 (10.0 haben 0,2746) und höher), können Sie diesen kleinen Trick tatsächlich einen Index verwenden:

hinzufügen OPTION (RECOMPILE) auf Ihre Anfrage, see Erland's article und SQL Server die OR aus (@LastName IS NULL OR LastName= @LastName) lösen, bevor der Abfrageplan ist basierend auf den Laufzeitwerten der lokalen Variablen erstellt, und ein Index kann verwendet werden.

Dies funktioniert für jede SQL Server-Version (korrekte Ergebnisse zurückgeben), aber nur die Option (RECOMPILE) enthalten, wenn Sie auf SQL 2008 SP1 CU5 (10.0.2746) und höher sind. Die Option (RECOMPILE) wird Ihre Abfrage neu kompilieren, nur die Verison, die aufgelistet wird, wird es basierend auf den aktuellen Laufzeitwerten der lokalen Variablen neu kompilieren, die Ihnen die beste Leistung geben werden. Wenn Sie diese Version von SQL Server 2008 nicht verwenden, lassen Sie diese Zeile einfach deaktiviert.

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

Seien Sie vorsichtig mit der UND/ODER-Priorität. UND hat Vorrang vor ODER, also ohne die richtigen Klammern erzeugt dieses Beispiel nicht die erwarteten Ergebnisse ... Es sollte also lauten: (@FirstName IS NULL OR (Vorname = @FirstName)) UND (@LastNameIS NULL ODER (Nachname = @LastName)) UND (@Titleis NULL ODER (Titel = @Titel)) – Bliek

+0

@Bliek, danke, ich habe es behoben. –

+3

Ihre Antwort ist großartig. Danke für die Info! –

18

Sie 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 

jedoch auf Daten hängen manchmal bessere dynamische Abfrage erstellen und ausführen.

6

Erweitern Sie Ihre WHERE Zustand:

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

i. e. Kombiniere verschiedene Fälle mit booleschen Bedingungen.

-3

Dies 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)) 
19

Die Antwort von @KM gut ist, soweit es nicht geht, aber voll und ganz auf einer seiner frühen Bits der Beratung bis folgen;

..., ignorieren Sie kompakten Code, ignorieren Sie sich Sorgen über das Wiederholen von Code, ...

Wenn Sie die beste Leistung erzielen möchten, sollten Sie eine maßgeschneiderte Abfrage für jede mögliche Kombination optionaler Kriterien schreiben. Dies mag extrem klingen, und wenn Sie viele optionale Kriterien haben, dann ist es vielleicht, aber die Leistung ist oft ein Kompromiss zwischen Aufwand und Ergebnissen. In der Praxis könnte es einen gemeinsamen Satz von Parameterkombinationen geben, die mit maßgeschneiderten Abfragen ausgerichtet werden können, und dann eine allgemeine Abfrage (wie bei 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 der Gemeinschafts von maßgeschneiderter behandelten Fällen fragen die Abfrage so effizient ist, wie es sein kann - dort von den unversorgten Kriterien keine Auswirkungen sind. Darüber hinaus können Indizes und andere Leistungsverbesserungen auf bestimmte maßgeschneiderte Abfragen ausgerichtet werden, anstatt alle möglichen Situationen zu erfüllen.

2

Fünf Jahre zu spät zur Party.

Es ist in den angegebenen Links der akzeptierten Antwort erwähnt, aber ich denke, dass es eine explizite Antwort auf SO verdient - dynamische Erstellung der Abfrage basierend auf bereitgestellten Parametern. Z.B .:

Setup-

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

Nutzungs

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

Pro:

  • leicht
  • Flexibilität zu schreiben und zu verstehen - leicht die Abfrage für trickier Filterungen erzeugen (z dynamische TOP)

Nachteile:

  • mögliche Performance-Probleme auf bereitgestellt Parameter abhängig, Indizes und Datenvolumen

keine direkte Antwort, aber das Problem auch bekannt als das große Bild im Zusammenhang

Normalerweise werden diese gespeicherten Filterprozeduren nicht umherschweben, sondern von einer bestimmten Dienstschicht aufgerufen. Dadurch bleibt die Möglichkeit bestehen, die Geschäftslogik (Filterung) von SQL in die Serviceebene zu verschieben.

Ein Beispiel wird unter Verwendung von LINQ2SQL die Abfrage zu erzeugen, basierend auf bereitgestellt Filter:

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:

  • dynamisch generierte Abfrage basierend auf Filter vorgesehen.Keine parameter sniffing oder recompile Hinweise benötigten
  • etwas leichter für die in der OOP Welt
  • typischerweise -leistung freundlich, da „einfache“ Abfragen ausgegeben werden, um zu schreiben (entsprechende Indizes sind allerdings noch erforderlich)

Nachteile:

  • LINQ2QL Einschränkungen erreicht werden können, und eine Herabstufung auf LINQ2Objects zwingen oder zurück zur reinen SQL-Lösung gehen, je nach Fall
  • unvorsichtig Schreiben von LINQ könnte schreckliche Abfragen generieren (oder viele Abfragen, wenn Navigationseigenschaften geladen)