2016-03-21 7 views
6

KontextSQL-WHERE IN aus param oder variable

Wir sind derzeit dabei, eine SQL-Datenbank für die Säuberung und wir haben über eine große Menge von Stored Procedures kommen, die nur geringe Unterschiede zwischen ihnen. Wir möchten sie in einem einzigen Prozess zusammenführen, um die Wartung zu vereinfachen.

Problem

Dies sind nur zwei Beispiele für die Art von gespeicherten Procs sind versuchen wir (tun Anmerkung, diese sind Versionen vereinfacht, nicht die tatsächlichen Procs) zu fusionieren.

Stored Procedure - Aktuelle Buchungen

ALTER PROCEDURE [dbo].[SelectCurrentBookings] 
    @client_FK INT, @startDate DATETIME, @endDate DATETIME 
AS 
BEGIN 
    SELECT ROW_NUMBER() OVER (ORDER BY [Booking].[bookingDateTime])   [RowNumber], 
      [Booking].[booking_PK]  [Booking ID], 
      [Booking].[bookingDateTime] [Booking Date], 
      [Booking].[bookingDuration] [Duration], 
      [Booking].[client]   [Client Name], 
      CASE WHEN [Booking].[bookingStatusCode_FK] IN (4,8,9,7,16) THEN 1 ELSE 0 END [Unserviced], 
    FROM [Booking] 
    WHERE [client_FK] = @client_FK 
      AND [Booking].[bookingStatusCode_FK] IN (1,2,14,17) 
      AND [Booking].[bookingDateTime] >= @startDate 
      AND [Booking].[bookingDateTime] < DATEADD(d,1,@endDate) 
      AND [Booking].[deleted] = 0 
END 

Stored Procedure - archivierten Buchungen

ALTER PROCEDURE [dbo].[SelectArchivedBookings] 
    @client_FK INT, @startDate DATETIME, @endDate DATETIME 
AS 
BEGIN 
    SELECT ROW_NUMBER() OVER (ORDER BY [Booking].[bookingDateTime])   [RowNumber], 
      [Booking].[booking_PK]  [Booking ID], 
      [Booking].[bookingDateTime] [Booking Date], 
      [Booking].[bookingDuration] [Duration], 
      [Booking].[client]   [Client Name], 
      CASE WHEN [Booking].[bookingStatusCode_FK] IN (4,8,9,7,16) THEN 1 ELSE 0 END [Unserviced], 
    FROM [Booking] 
    WHERE [client_FK] = @client_FK 
      AND [Booking].[bookingStatusCode_FK] IN (1,2,14,4,9,7,16,13) 
      AND [Booking].[bookingDateTime] >= @startDate 
      AND [Booking].[bookingDateTime] < DATEADD(d,1,@endDate) 
      AND [Booking].[deleted] = 0 
END 

Der Code, der die gespeicherten Procs ruft in VB.NET

Dim Command As DbCommand = _db.GetStoredProcCommand("SelectCurrentBookings") 
_db.AddInParameter(Command, "client_FK", DbType.Int32, ClientID) 
_db.AddInParameter(Command, "startDate", DbType.DateTime, StartDate) 
_db.AddInParameter(Command, "endDate", DbType.DateTime, EndDate) 
Return _db.ExecuteDataSet(Command) 

Wie Sie sehen können, besteht der einzige Unterschied zwischen den oben gespeicherten Prozeduren in den Werten, die dem WHERE IN übergeben werden.

Gibt es eine Möglichkeit für uns, dies zu ändern und die Liste der Werte über einen Parameter oder eine Variable zu liefern?

+1

Sie können [Tabellenwerte] (https://msdn.microsoft.com/en-us/library/bb675163%28v=vs.110%29.aspx) verwenden. In diesem Fall würden Sie zuerst den Tabellenwert in VB füllen (z. B. mit 4 Zeilen 1,2,14,17) und die IN-Klausel durch eine Verknüpfung zu diesem Tabellenwert-Parameter ersetzen. In meinem Link finden Sie einige Beispiele dafür. –

+0

Beachten Sie, dass "SQL Server keine Statistiken zu Tabellenwerten verwaltet". Die Abfragepläne können verrückt werden. –

+0

@AlexB. Vielen Dank für diesen Vorschlag, ich bin mir nicht sicher, ob ich das richtig verstehe, aber würde das nicht zu Nebenläufigkeitsproblemen führen? Auch Michael erklärte, dass Abfragepläne damit verrückt werden könnten, was für uns auch nicht ideal ist. – theoriginalbit

Antwort

1

Wenn es das Ziel ist, den Wartungsaufwand zu reduzieren, würde ich demütig vorschlagen, dass das Verschieben von Datenwerten aus der Datenschicht und deren hartes Codieren in der Logikschicht vielleicht an mehreren Stellen vielleicht nicht zu diesem Ziel beiträgt.

Durch Entfernen dieser expliziten Werte aus den Abfragen werden Informationen entfernt, die der Optimierer zum Erstellen eines Plans verwenden kann. Seien Sie vorsichtig, wenn Sie es durch etwas anderes ersetzen, sonst könnte die Abfrageleistung darunter leiden. Ich würde postulieren, dass diese SPs genau getrennt sind, so dass für jeden einzelne Pläne existieren, besonders wenn die Abfragen viel komplexer sind als gezeigt. Vergleichen Sie die aktuellen Pläne miteinander und was auch immer Sie tun, um sicherzustellen, dass Sie sich nicht zurückgemeldet haben.

Eine Option kann eine neue „Liste“ Tabelle zu erstellen:

ListName StatusCode 
current 1 
current 2 
... 
current 17 
archive 1 
archive 2 
archive 4 
... 
archive 16 

diese Tabelle Join stattdessen eine IN-Klausel zu verwenden. Qualifizieren Sie den Join durch ListName, der als Parameter übergeben wird. Ein eindeutiger gruppierter Index auf (ListName, StatusCode) wäre gut. Sie können eine gefilterte Statistik für jeden ListName erstellen. Erstellen Sie eine Fremdschlüsseleinschränkung, wenn Sie eine Hauptliste mit Statuswerten haben.

Die gespeicherte Prozedur wird dann

ALTER PROCEDURE [dbo].[SelectCurrentBookings] 
    @client_FK INT, @startDate DATETIME, @endDate DATETIME, 
    @ListName char(10) 
AS 
BEGIN 
    SELECT ROW_NUMBER() OVER (ORDER BY [Booking].[bookingDateTime]) [RowNumber], 
      ... 
    FROM [Booking] 
    INNER JOIN dbo.List as l 
     ON [Booking].[bookingStatusCode_FK] = l.StatusCode 
     AND l.ListName = @ListName 
    WHERE [client_FK] = @client_FK 
      AND [Booking].[bookingDateTime] >= @startDate 
      AND [Booking].[bookingDateTime] < DATEADD(d,1,@endDate) 
      AND [Booking].[deleted] = 0 
END 

Der Aufrufcode erhält einen Parameter

Dim Command As DbCommand = _db.GetStoredProcCommand("SelectCurrentBookings") 
_db.AddInParameter(Command, "client_FK", DbType.Int32, ClientID) 
_db.AddInParameter(Command, "startDate", DbType.DateTime, StartDate) 
_db.AddInParameter(Command, "endDate", DbType.DateTime, EndDate) 
_db.AddInParameter(Command, "ListName", DbType.String, "current") //correct type needed 
Return _db.ExecuteDataSet(Command) 

Auf diese Weise Bedeutungen für Statuscodes sind in einem Ort aufgezeichnet und gute Statistiken sind Optimiser zur Verfügung. Ob dies schneller ist als die aktuelle Implementierung, kann nur das Testen erkennen.

+0

Wir haben tatsächlich eine Lookup-Tabelle eingerichtet, die alle möglichen Statuscodes enthält. Ich verstehe nicht ganz, wie das, was du vorschlägst, funktionieren würde, hauptsächlich darum, wie wir dem Beitritt mitteilen würden, welcher Status 'archiviert ist und welcher nicht, damit er die passenden auswählen kann. Können Sie uns bitte weitere Informationen geben? – theoriginalbit

+0

Eine Tabelle aller Statuscodes ist gut. Es kann verwendet werden, um gute Werte in der Listentabelle durch einen FK sicherzustellen.So wird zum Beispiel der Statuscode 4 in den Abfragen "current" und "archive" verwendet, so dass die von Ihnen angegebene aktuelle Tabelle nicht ausreicht. Die neue Tabelle, die ich vorschlage, ist erforderlich. –

-1

"Wir möchten sie in einem einzigen Prozess konsolidieren, um die Wartung zu vereinfachen."

Ich würde zustimmen, mehrere gespeicherte Prozedur ist, die für die gleiche Sache mit Ausnahme einer bestimmten Bedingung tun Wartung sein kann Hölle; vor allem, wenn diese überall sind. Im Folgenden habe ich ein paar verschiedene Optionen und Lösungen, die Sie ausprobieren können. In diesem Sinne werde ich lassen Sie entscheiden, wie Sie dies als jede Option nähern möchte, ist anders als in Ausführungspläne usw.

„der einzige Unterschied zwischen den oben gespeichert Procs werden die Werte in der mitgelieferten WO IN“

Option One

  • eine Systemtabelle zum Beispiel erstellen: dbo.System_Booking_Status. Diese Tabelle würde nur ein paar Spalten: System_Status_ID (Primärschlüssel), Booking_Status_Code INT NOT NULL und Booking_Status BIT NOT NULL

Jetzt können Sie diese Tabelle mit Ihren Werten füllen. Die Buchungsstatusspalte könnte 1 für aktuell und 2 für Archiv bedeuten; wie auch immer Sie es wünschen. Oder machen Sie einen VarChar(15) Typ und verwenden Sie eine Beschreibung (Current oder Archived), so dass unterschieden wird OR Char(1) Typ und Verwendung (C - für aktuelle & A - für die Archivierung).

Nun, da Sie eine Systemtabelle haben Sie - beitreten können, müssen wir die gespeicherte Prozedur sagen, dies zu tun und wann. Ich habe einen neuen Parameter mit dem Namen Status verwendet, der übergeben werden müsste, wenn Sie den Aufruf ausführen, um entweder die aktuellen und/oder archivierten Datensätze zurückzuholen.

ALTER PROCEDURE [dbo].[SelectCurrentBookings] 
    @client_FK INT, @startDate DATETIME, @endDate DATETIME, @status INT 
AS 
BEGIN 
    IF @status = 1 --current 
    BEGIN 
    SELECT ROW_NUMBER() OVER (ORDER BY b.[bookingDateTime])   [RowNumber], 
      b.[booking_PK]  [Booking ID], 
      b.[bookingDateTime] [Booking Date], 
      b.[bookingDuration] [Duration], 
      b.[client]   [Client Name], 
      CASE WHEN b.[bookingStatusCode_FK] IN (4,8,9,7,16) THEN 1 ELSE 0 END [Unserviced], 
    FROM [Booking] b 
    INNER JOIN dbo.System_Booking_Status sbs 
    ON sbs.Booking_Status_Code = b.bookingStatusCode_FK 
     AND sbs.Booking_Status = 1 --current? 
    WHERE [client_FK] = @client_FK 
      AND b.[bookingDateTime] >= @startDate 
      AND b.[bookingDateTime] < DATEADD(d,1,@endDate) 
      AND b.[deleted] = 0 
    END 
     ELSE 
    BEGIN 
    SELECT ROW_NUMBER() OVER (ORDER BY b.[bookingDateTime])   [RowNumber], 
      b.[booking_PK]  [Booking ID], 
      b.[bookingDateTime] [Booking Date], 
      b.[bookingDuration] [Duration], 
      b.[client]   [Client Name], 
      CASE WHEN b.[bookingStatusCode_FK] IN (4,8,9,7,16) THEN 1 ELSE 0 END [Unserviced], 
    FROM [Booking] b 
    INNER JOIN dbo.System_Booking_Status sbs 
    ON sbs.Booking_Status_Code = b.bookingStatusCode_FK 
     AND sbs.Booking_Status = 2 --archived? 
    WHERE [client_FK] = @client_FK 
      AND b.[bookingDateTime] >= @startDate 
      AND b.[bookingDateTime] < DATEADD(d,1,@endDate) 
      AND b.[deleted] = 0 
    END 
END 

Jetzt müssen Sie nicht hart codierte Werte verwenden und die IN clause zu eliminieren, indem die INNER JOIN verwenden.

Option Zwei (Quick n Schmutzige)

  • Pass in einer anderen Variable, um entweder festzustellen, ob Sie wieder aktuelle oder archivierten Aufzeichnungen wollen.

    IF @status = 1 .... BEGIN --Normal Abfrage für aktive ausgeführt ... END BEGIN ELSE --Normal Abfrage nach archivierten ausgeführt ... END

Option Drei

Sie können in XML der Werte übergeben, die man braucht. Parsen Sie diese XML out in eine Table Variable und dann zutreten. Es würde Ihre IN Klausel und besseren Ausführungsplan auch beseitigen.

Zum Beispiel:

Declare @Status_Codes XML = '' 

DECLARE @StatusTable TABLE 
(
    Status_Code INT 
) 

-- Parse the XML in to the temp table declared above 
INSERT INTO @StatusTable(Status_Code) 
SELECT 
    xmlData.A.value('.', 'INT') 
FROM @Status_Codes.nodes('BookingStatus/StatusCode/Code') xmlData(A) 

Jetzt haben Sie ein Table Variable Sie beitreten können ...

FROM [Booking] b 
INNER JOIN @StatusTable s 
ON s.Status_Code = b.bookingStatusCode_FK 

Als nächstes müssen Sie in diese Daten gelangen von Ihrem Funktionsaufruf. Sie können eine Funktion erstellen, die das formatierte XML zurückgibt und in Ihrem Aufruf der gespeicherten Prozedur übergibt.

Dann machen Sie Ihren Anruf, bevor Sie ihn an Ihre Funktion übergeben ODER den Anruf in Ihrer Funktion tätigen; Sie haben mehr als ein paar Möglichkeiten ...

Auf einem anderen Hinweis

Ich bemerke Sie eine DataSet ausgeführt werden, aber sie kehren nie, würde ich die DataTable.Load Methode verwenden, die einen Leser ausführt und füllt ein Tabelle ...

Viel Glück!

+1

@ downvoter bitte hinzufügen, was verbessert werden könnte und was ist falsch mit der Antwort? Es ist traurig, ohne Erklärung zu kommen und runter zu kommen. Alle ** 3 ** Optionen funktionieren und wurden getestet ... – Codexer