2016-03-21 13 views
6

ContestoSQL DOVE IN dal param o variabile

Siamo attualmente in fase di ripulire un database SQL e abbiamo incontrato una grande quantità di stored procedure che hanno solo lievi differenze tra di loro. Vogliamo consolidarli in un unico processo per facilitare la manutenzione.

Problema

Qui di seguito sono solo due esempi del tipo di stored procedure che stiamo cercando di unire (fare nota, queste sono le versioni, non i procs effettivi semplificati).

stored procedure - Prenotazioni attuali

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

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 

Il codice che invoca le stored procedure è 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) 

Come si può vedere, l'unica differenza tra i proc memorizzati sopra sono i valori forniti a WHERE IN.

C'è un modo per noi di modificare questo e avere l'elenco dei valori forniti attraverso un parametro o una variabile?

+1

È possibile utilizzare [parametri con valori di tabella] (https://msdn.microsoft.com/en-us/library/bb675163%28v=vs.110%29.aspx). In questo caso, si riempie il valore di tabella in VB per primo (ad es. Con 4 righe 1,2,14,17) e si sostituisce la clausola IN con un join a quel parametro del valore di tabella. Nel mio link ci sono alcuni esempi su come farlo. –

+0

Tenere presente che "SQL Server non conserva statistiche sui parametri con valori di tabella." I piani di query potrebbero diventare pazzi. –

+0

@AlexB. grazie per questo suggerimento, non sono sicuro se sto capendo correttamente, ma non introdurrei alcuni problemi di concorrenza? Anche Michael ha dichiarato che i piani di ricerca potrebbero impazzire con questo, che non è l'ideale neanche per noi. – theoriginalbit

risposta

1

Se l'obiettivo è ridurre lo sforzo di manutenzione, suggerirei umilmente che spostare i valori dei dati dal livello dati e codificarli nel livello logico, magari in più punti, potrebbe non contribuire a questo obiettivo.

La rimozione di questi valori espliciti dalle query rimuoverà le informazioni che l'ottimizzatore può utilizzare per creare un piano. Fai attenzione a sostituirlo con qualcosa di meglio che le prestazioni della query potrebbero risentirne. Direi che questi SP sono separati con precisione in modo che esistano piani unici per ciascuno, specialmente se le query sono molto più complesse di quelle mostrate. Confronta i piani attuali gli uni contro gli altri e qualunque cosa tu finisca per assicurarti di non essere regredito.

Una possibilità potrebbe essere quella di creare un nuovo "lista" tavolo:

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

Iscriviti per questa tabella invece di utilizzare una clausola IN. Qualifica il join da ListName, che viene passato come parametro. Un indice cluster univoco su (ListName, StatusCode) sarebbe buono. Si può prendere in considerazione la creazione di una statistica filtrata per ogni NomeElenco. Creare un vincolo di chiave esterna se si mantiene un elenco principale di valori di stato.

La stored procedure diventa allora

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 

Il codice chiamante guadagna un parametro

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) 

In questo modo significati per codici di stato sono registrati in un posto e buone statistiche sono a disposizione del ottimizzatore. Se questo è più veloce dell'attuale implementazione, solo i test possono dirlo.

+0

In realtà abbiamo una configurazione tabella di ricerca che contiene tutti i possibili codici di stato. Non capisco bene come funzionerebbe, ma soprattutto su come comunichiamo al join quale stato 'sono archiviati e quali no, in modo che possa selezionare quelli appropriati. Sei in grado di fornire ulteriori informazioni? – theoriginalbit

+0

Una tabella di tutti i codici di stato è buona. Può essere usato per assicurare buoni valori nella tabella List da un FK.Il codice di stato 4, ad esempio, viene utilizzato nelle query "corrente" e "archivio", pertanto la tabella corrente che menzioni non è sufficiente. È necessario il nuovo tavolo che propongo. –

-1

"Vogliamo consolidarli in un unico processo per facilitare la manutenzione."

sarei d'accordo, avendo stored procedure multiple di che fare la stessa cosa, tranne una certa condizione può essere la manutenzione inferno; soprattutto se questi sono dappertutto. Di seguito ho alcune opzioni e soluzioni diverse che potresti provare. Con questo in mente, io vi permetterà di decidere come si vorrebbe avvicinarsi a questo come ogni opzione è diversa, come nei piani di esecuzione ecc

"l'unica differenza tra i procs memorizzati sopra sono i valori forniti alla DOVE iN"

Opzione uno

  • Creare una tabella di sistema, ad esempio: dbo.System_Booking_Status. Questa tabella avrebbe solo bisogno di un paio di colonne: System_Status_ID (chiave primaria), Booking_Status_Code INT NOT NULL e Booking_Status BIT NOT NULL

Ora è possibile popolare la tabella con i propri valori. La colonna dello stato di prenotazione potrebbe significare 1 per corrente e 2 per archivio; comunque tu desideri. Oppure rendi un tipo VarChar(15) e utilizza una descrizione (Corrente o Archiviato) in modo che sia distinto O un tipo Char(1) e utilizzi (C - per l'attuale & A - per l'archivio).

Ora che avete una tabella di sistema si possibile aderire al abbiamo bisogno di dire la stored procedure di farlo e quando. Ho usato un nuovo parametro chiamato: stato che dovrebbe essere passato quando si effettua la chiamata per ottenere i record correnti o archiviati.

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 

Ora non c'è bisogno di utilizzare i valori hard coded ed eliminare il IN clause utilizzando la INNER JOIN.

Opzione due (Quick n sporca)

  • Passo in un'altra variabile a uno determinare se si desidera che i record attuali o archiviati indietro.

    IF @status = 1 .... BEGIN interrogazione --Normal eseguito per attivi ... END ELSE BEGIN interrogazione --Normal giustiziato per archiviato ... FINE

Opzione Tre

È possibile passare XML dei valori che si avrebbe bisogno. Analizzare questo XML in un Table Variable e quindi partecipare a questo. Elimina la tua clausola IN e un piano di esecuzione migliore.

Ad esempio:

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) 

Ora avete un Table Variable si può aderire a ...

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

Avanti devi passare questi dati dal tuo chiamata di funzione. È possibile creare una funzione che restituisca il formato XML e trasferirlo nella chiamata alla procedura memorizzata.

Public Shared Function ConstructXMLStatusCodes(ByVal intStatus As Integer) As String 
    Dim strBuilder As New System.Text.StringBuilder 
    If intStatus = 1 'active... 
    With strBuilder 
     .AppendLine("<BookingStatus>") 
     .AppendLine("<StatusCode>") 
     .AppendLine("<Code>1</Code>") 
     .AppendLine("<Code>2</Code>") 
     'continue to add more codes... 
     .AppendLine("</StatusCode>") 
     .AppendLine("</BookingStatus>") 
    End With 
    Else 
     'Create your builder with the archive... 
    End If 

    Return strBuilder.ToString() 
End Function 

Quindi effettuare la chiamata prima di passarlo alla funzione o effettuare la chiamata nella funzione; si dispone di più di alcune scelte ...

In un'altra nota

Ho notato che sta eseguendo un DataSet, ma non si è mai tornare un, vorrei utilizzare il metodo DataTable.Load che esegue un lettore e colma una tavolo ...

Buona fortuna!

+1

@downvoter si prega di aggiungere cosa potrebbe essere migliorato e cosa c'è di sbagliato nella risposta? È triste venire avanti e giù senza spiegazioni. Tutte le opzioni ** 3 ** funzioneranno e saranno state testate ... – Codexer