Ho una visione ampia che utilizzo all'interno di un'applicazione. Penso di aver ridotto il mio problema di prestazioni, ma non sono sicuro di come risolverlo. Una versione semplificata della vista è simile alla seguente:
SELECT ISNULL(SEId + '-' + PEId, '0-0') AS Id,
*,
DATEADD(minute, Duration, EventTime) AS EventEndTime
FROM (
SELECT se.SEId, pe.PEId,
COALESCE(pe.StaffName, se.StaffName) AS StaffName, -- << Problem!
COALESCE(pe.EventTime, se.EventTime) AS EventTime,
COALESCE(pe.EventType, se.EventType) AS EventType,
COALESCE(pe.Duration, se.Duration) AS Duration,
COALESCE(pe.Data, se.Data) AS Data,
COALESCE(pe.Field, se.Field) AS Field,
pe.ThisThing, se.OtherThing
FROM PE pe FULL OUTER JOIN SE se
ON pe.StaffName = se.StaffName
AND pe.Duration = se.Duration
AND pe.EventTime = se.EventTime
WHERE NOT(pe.ThisThing = 1 AND se.OtherThing = 0)
) Z
Ciò probabilmente non giustifica l'intero motivo della struttura della query, ma forse ti dà un'idea: questa vista unisce due tabelle progettate molto male su cui non ho il controllo e cerca di sintetizzare alcune informazioni da esso.
Quindi, poiché questa è una vista utilizzata dall'applicazione, mentre provo a ottimizzare la avvolgo in un'altra SELEZIONA, in questo modo:
SELECT * FROM (
-- … above code …
) Q
WHERE StaffName = 'SMITH, JOHN Q'
perché l'applicazione sta cercando membri dello staff specifici nel risultato.
Il problema sembra essere la COALESCE(pe.StaffName, se.StaffName) AS StaffName
sezione e che sto selezionando dalla vista in poi StaffName
. Se lo cambio su pe.StaffName AS StaffName
o se.StaffName AS StaffName
, i problemi di prestazioni scompaiono (ma vedi l'aggiornamento 2 di seguito) . Ma ciò non funzionerà perché una parte o l'altra FULL OUTER JOIN
potrebbero mancare, quindi l'uno o l'altro campo potrebbe essere NULL.
Posso riformattare questo sostituendo il COALESCE(…)
con qualcos'altro, che verrà riscritto nella sottoquery?
Altre note:
- Ho già aggiunto alcuni indici per risolvere i problemi di prestazioni con il resto della query, senza
COALESCE
che sia molto veloce. - Con mia grande sorpresa, guardare al piano di esecuzione non solleva alcun flag, anche quando la subquery e la
WHERE
dichiarazione di wrapping sono incluse. Il mio costo totale di subquery nell'analizzatore è0.0065736
. Mah. Sono necessari quattro secondi per l'esecuzione. - Cambiare l'applicazione in modo diverso per interrogare
(es. Tornarepotrebbe funzionare, ma come ultima risorsa - Spero davvero di poter ottimizzare la vista senza dover ricorrere a toccare l'applicazione.pe.StaffName AS PEStaffName, se.StaffName AS SEStaffName
e fareWHERE PEStaffName = 'X' OR SEStaffName = 'X'
) - Una procedura memorizzata avrebbe probabilmente più senso per questo, ma l'applicazione è costruita con Entity Framework e non sono riuscito a capire come farlo funzionare bene con un SP che restituisce un tipo di tabella (un altro argomento interamente).
indici
Gli indici che ho aggiunto finora assomigliano a questo:
CREATE NONCLUSTERED INDEX [IX_PE_EventTime]
ON [dbo].[PE] ([EventTime])
INCLUDE ([StaffName],[Duration],[EventType],[Data],[Field],[ThisThing])
CREATE NONCLUSTERED INDEX [IX_SE_EventTime]
ON [dbo].[SE] ([EventTime])
INCLUDE ([StaffName],[Duration],[EventType],[Data],[Field],[OtherThing])
Aggiornare
Hmm ... Ho provato a simulare il cambiamento colpito sopra, e non ha aiutato. Cioè, prima ) Z
sopra, ho aggiunto AND (pe.StaffName = 'SMITH, JOHN Q' OR se.StaffName = 'SMITH, JOHN Q')
, ma le prestazioni sono le stesse. Ora non so davvero da dove cominciare.
Aggiornamento 2
Il commento di @ypercube sulla necessità del full join mi ha fatto capire che la mia query sintetizzata ha lasciato fuori un componente probabilmente importante. Mentre, sì, ho bisogno del join completo, il test che ho fatto sopra facendo cadere COALESCE
e testando solo un lato del join per un valore non nullo renderebbe l'altro lato del join completo irrilevante e probabilmente l'ottimizzatore lo stava usando fatto per velocizzare la query. Inoltre, ho aggiornato l'esempio per mostrare che in StaffName
realtà è una delle chiavi di join - che probabilmente ha un impatto significativo sulla domanda. Ora sto anche propendendo al suo suggerimento che suddividere questo in un'unione a tre invece che in unione completa possa essere la risposta e semplificherà l'abbondanza di cose COALESCE
che sto facendo comunque. Provandolo ora.
KeyField
, entrambi indicizza INCLUDE
il StaffName
campo e molti altri campi. Posso pubblicare le definizioni dell'indice nella domanda. Ci sto lavorando su un server di prova in modo da poter aggiungere tutti gli indici che ritieni possano essere utili da provare!
WHERE pe.ThisThing = 1 AND se.OtherThing = 0
condizione che annulla il FULL OUTER
join e rende la query equivalente a un join interno. Sei sicuro di aver bisogno di un abbonamento COMPLETO?
INNER JOIN
, LEFT JOIN
con WHERE IS NULL
segno di spunta, DESTRA UNISCITI con IS NULL) e poi UNION ALL
le tre parti. In questo modo non sarà necessario utilizzarlo COALESCE()
e potrebbe (potrebbe) aiutare l'ottimizzatore a capire la riscrittura.