2013-11-01 21 views
6

Ho quattro tabelle, TopLevelParent, due tabelle di livello medio MidParentA e MidParentB e una tabella figlio che può avere un parent di MidParentA o MidParentB (Uno o l'altro midParent deve essere in posto). Entrambe le tabelle di livello medio hanno una tabella padre di TopLevelParent.SQL Server Left Join con operatore "Or"

Il look tavolo di primo livello in questo modo:

TopLevelId | Name 
-------------------------- 
1   | name1 
2   | name2 

Le tabelle MidParent simile a questa:

MidParentAId | TopLevelParentId |   MidParentBId | TopLevelParentId | 
------------------------------------  ------------------------------------ 
1   |  1   |   1   |  1   | 
2   |  1   |   2   |  1   | 

tabella figlio simile a questa:

ChildId | MidParentAId | MidParentBId 
-------------------------------- 
1  |  1  | NULL 
2  | NULL  |  2 

ho usato la seguente a sinistra si unisce a una stored procedure più grande che sta scadendo, e assomiglia all'operato OR r l'ultimo sinistra join è il colpevole:

SELECT *  
FROM TopLevelParent tlp 
LEFT JOIN MidParentA a ON tlp.TopLevelPatientId = a.TopLevelPatientId 
LEFT JOIN MidParentB a ON tlp.TopLevelPatientId = b.TopLevelPatientId 
LEFT JOIN Child c ON c.ParentAId = a.ParentAId OR c.ParentBId = b.ParentBId 

Esiste un modo più performante per farlo aderire?

risposta

2

Ecco quello che ho fatto, alla fine, che ha ottenuto il tempo di esecuzione giù da 52 secondi a 4 secondi.

SELECT * 
FROM (
    SELECT tpl.*, a.MidParentAId as 'MidParentId', 1 as 'IsMidParentA' 
    FROM TopLevelParent tpl 
    INNER JOIN MidParentA a ON a.TopLevelParentId = tpl.TopLevelParentID 
UNION 
    SELECT tpl.*, b.MidParentBId as 'MidParentId', 0 as 'IsMidParentA' 
    FROM TopLevelParent tpl 
    INNER JOIN MidParentB b ON b.TopLevelParentId = tpl.TopLevelParentID 
UNION 
    SELECT tpl.*, 0 as 'MidParentId', 0 as 'IsMidParentA' 
    FROM TopLevelParent tpl 
    WHERE tpl.TopLevelParentID NOT IN (
     SELECT pa.TopLevelParentID 
     FROM TopLevelParent tpl 
     INNER JOIN MidParentA a ON a.TopLevelParentId = tpl.TopLevelParentID 
    UNION 
     SELECT pa.TopLevelParentID 
     FROM TopLevelParent tpl 
     INNER JOIN MidParentB b ON h.TopLevelParentId = tpl.TopLevelParentID 
    ) 
) tpl 
LEFT JOIN MidParentA a ON a.TopLevelParentId = tpl.TopLevelParentID 
LEFT JOIN MidParentB b ON b.TopLevelParentId = tpl.TopLevelParentID 
LEFT JOIN 
(
     SELECT [ChildId] 
       ,[MidParentAId] as 'MidParentId' 
       ,1 as 'IsMidParentA' 
     FROM Child c 
     WHERE c.MidParentAId IS NOT NULL 
    UNION 
     SELECT [ChildId] 
       ,[MidParentBId] as 'MidParentId' 
       ,0 as 'IsMidParentA' 
     FROM Child c 
     WHERE c.MidParentBId IS NOT NULL 
) AS c 
ON c.MidParentId = tpl.MidParentId AND c.IsMidParentA = tpl.IsMidParentA 

Questo elimina la scansione tabella che stava accadendo, come ho eguagliato il record di livello superiore al suo genitore medio livello in anticipo se esiste, e timbrato su quel disco.

Ho anche fatto lo stesso con il record figlio che significa che posso poi basta aderire al record figlio al record di alto livello sulla MidParentId, e uso il bit di flag IsMidParentA per differenziare dove ci sono due MidParentIds identici (vale a dire un Id di 1 per IsMidParentA e IsMidParentB).

Grazie a tutti quelli che hanno avuto il tempo di rispondere.

+3

Questo è un affare complesso – Roel

2

Si consiglia di utilizzare i predicati all'interno di On.

"È molto importante capire che, con i join esterni, le clausole ON e WHERE svolgono ruoli molto diversi e, pertanto, non sono intercambiabili.La clausola WHERE svolge ancora un semplice ruolo di filtro, vale a dire, mantiene casi veri e rigetti casi falsi e sconosciuti. Usa qualcosa del genere e usa i predicati nella clausola where.Tuttavia, la clausola ON non svolge un semplice ruolo di filtro, anzi, è più un ruolo corrispondente. In altre parole, una riga nel verrà restituito il lato conservato, indipendentemente dal fatto che il predicato ON trovi o meno una corrispondenza. Quindi il predicato ON determina solo quali righe dal lato non protetto vengono abbinate alle righe dal lato conservato, non se restituire le righe dal lato conservato. " ** Esame 70-461: interrogazione di Microsoft SQL Server 2012

9

Dato il numero limitato di query che vengono visualizzate; una regola empirica molto approssimativa è quella di sostituire un Or con un'Unione per evitare la scansione della tabella.

Select.. 
LEFT JOIN Child c ON c.ParentAId = a.ParentAId 
union 
Select.. 
left Join Child c ON c.ParentBId = b.ParentBId 
+0

Ho modificato la domanda per mostrare più della query –

+1

Ciò aiuta :) Potresti avere due join per Child piuttosto che uno ciascuno che fa un lato dell'OR; e quindi utilizzare la coalescenza per ottenere un valore da C o D in cui si desidera solo il valore 1 – u07ch

0

un altro modo di scrivere:

LEFT JOIN Child c ON c.ParentAId = COALESCE(a.ParentAId, b.ParentBId)

Modifica

Un possibile approccio sta interrogando prima il MidParentA e poi il MidParentB e poi UNION i risultati:

SELECT tlp.*, 
     a.MidParentAId, 
     null MidParentBId, 
     c.ChildId 
FROM TopLevelParent tlp 
LEFT JOIN MidParentA a ON tlp.TopLevelPatientId = a.TopLevelPatientId 
LEFT JOIN Child c ON c.MidParentAId = a.MidParentAId 
UNION 
SELECT tlp.*, 
     null MidParentAId, 
     b.MidParentBId, 
     c.ChildId 
FROM TopLevelParent tlp 
LEFT JOIN MidParentB b ON tlp.TopLevelPatientId = b.TopLevelPatientId 
LEFT JOIN Child c ON c.MidParentBId = b.MidParentBId 

Una demo in SQLFiddle

+0

La query originale aveva c.ParentBId = b.ParentBId anziché c.parentAID = b.parentBID, quindi questo potrebbe non essere corretto – u07ch

+0

hai ragione , a prima vista non ho letto attentamente la domanda – mucio