2009-08-31 18 views
11

Qual è il modo più efficiente per scrivere un'istruzione select simile alla seguente.Qual è il modo più efficace per scrivere un'istruzione select con una sottoquery "not in"?

SELECT * 
FROM Orders 
WHERE Orders.Order_ID not in (Select Order_ID FROM HeldOrders) 

L'essenza è che si desidera i record da una tabella quando l'elemento non è in un'altra tabella.

+5

Il modo migliore è provare i vari approcci e esaminare i piani di esecuzione. – pjp

+0

Nella mia situazione SQL Server 2000, dati gli indici sulle tabelle in questione, la query "Join" era la più veloce. SELEZIONA * Dagli Ordini o LEFT JOIN HeldOrders h on o.Order_ID = h.Order_ID e h.Order_ID è nullo – Stimy

risposta

7

"La maggior parte efficiente" sta per essere diverso a seconda tavoli formati , indici e così via. In altre parole, sarà diverso a seconda del caso specifico che stai utilizzando.

Ci sono tre modi che uso comunemente per ottenere ciò che si desidera, a seconda della situazione.

1. L'esempio funziona correttamente se Orders.order_id è indicizzato e HeldOrders è piuttosto piccolo.

2. Un altro metodo è il "subquery correlata", che è una leggera variazione di quello che hai ...

SELECT * 
FROM Orders o 
WHERE Orders.Order_ID not in (Select Order_ID 
           FROM HeldOrders h 
           where h.order_id = o.order_id) 

nota l'aggiunta della clausola dove. Questo tende a funzionare meglio quando HeldOrders ha un numero elevato di righe. Order_ID deve essere indicizzato in entrambe le tabelle.

3. Un altro metodo che uso a volte viene lasciato join esterno ...

SELECT * 
FROM Orders o 
left outer join HeldOrders h on h.order_id = o.order_id 
where h.order_id is null 

Quando si utilizza l'esterno sinistro join, h.order_id avrà un valore in esso corrispondenza o.order_id quando c'è una riga corrispondente. Se non c'è una riga corrispondente, h.order_id sarà NULL. Controllando i valori NULL nella clausola where puoi filtrare tutto ciò che non ha una corrispondenza.

Ognuna di queste variazioni può funzionare in modo più o meno efficiente in vari scenari.

+0

'@ Dave': perché usi' NOT IN' invece di 'NOT EXISTS' nel metodo' 2'? – Quassnoi

+1

@Quassnoi: Onestamente, probabilmente una brutta abitudine. Dopo aver letto la risposta sopra, ho intenzione di iniziare a utilizzare NOT EXISTS. –

+0

L'opzione 3 ha funzionato meglio nel mio scenario (SQL Server 2000 ha fornito gli indici delle tabelle). Penso che la migliore risposta sia testare un certo numero di metodi. – Stimy

4

È possibile utilizzare uno LEFT OUTER JOIN e verificare il numero NULL nella tabella corretta.

SELECT O1.* 
FROM Orders O1 
LEFT OUTER JOIN HeldOrders O2 
ON O1.Order_ID = O2.Order_Id 
WHERE O2.Order_Id IS NULL 
+1

Questo è * lontano * dall'essere un metodo più efficiente. – Quassnoi

+0

Questo non sarà necessariamente il metodo più efficiente. –

+0

È significativamente più efficace di una sottoquery, ma almeno esegue una sola volta contro la seconda tabella, invece che una volta/riga. – SqlRyan

19

Per cominciare, un link ad un vecchio articolo nel mio blog su come NOT IN predicato lavora in SQL Server (e in altri sistemi troppo):


Puoi riscriverlo come segue:

SELECT * 
FROM Orders o 
WHERE NOT EXISTS 
     (
     SELECT NULL 
     FROM HeldOrders ho 
     WHERE ho.OrderID = o.OrderID 
     ) 

, tuttavia, la maggior parte dei database tratterà queste query allo stesso modo.

Entrambe queste query useranno una specie di ANTI JOIN.

Questo è utile per SQL Server se si desidera controllare due o più colonne, dal momento che SQL Server non supporta questa sintassi:

SELECT * 
FROM Orders o 
WHERE (col1, col2) NOT IN 
     (
     SELECT col1, col2 
     FROM HeldOrders ho 
     ) 

Si noti, tuttavia, che NOT IN può essere difficile a causa del modo in cui tratta NULL valori.

Se Held.Orders è annullabile, nessun record si trovano e la sottoquery non restituisce, ma un unico NULL, tutta la query restituirà nulla (sia IN e NOT IN valuterà a NULL in questo caso).

Considerare questi dati:

Orders: 

OrderID 
--- 
1 

HeldOrders: 

OrderID 
--- 
2 
NULL 

seguente interrogazione:

SELECT * 
FROM Orders o 
WHERE OrderID NOT IN 
     (
     SELECT OrderID 
     FROM HeldOrders ho 
     ) 

tornerà nulla, che probabilmente non è quello che ci si aspetta.

Tuttavia, questo:

SELECT * 
FROM Orders o 
WHERE NOT EXISTS 
     (
     SELECT NULL 
     FROM HeldOrders ho 
     WHERE ho.OrderID = o.OrderID 
     ) 

tornerà la riga con OrderID = 1.

noti che LEFT JOIN soluzioni proposte da altri è lungi dall'essere una soluzione più efficiente.

interrogazione:

SELECT * 
FROM Orders o 
LEFT JOIN 
     HeldOrders ho 
ON  ho.OrderID = o.OrderID 
WHERE ho.OrderID IS NULL 

userà una condizione di filtro che dovrà valutare e filtrare tutti corrispondenza righe che possono essere Numerius

Un metodo ANTI JOIN utilizzato sia IN e EXISTS sarà Basta fare in modo che un record non esista una volta per ogni riga in Orders, quindi eliminerà prima tutti i possibili duplicati:

  • NESTED LOOPS ANTI JOIN e MERGE ANTI JOIN saranno saltare i duplicati nella valutazione HeldOrders.
  • A HASH ANTI JOIN eliminerà i duplicati durante la creazione della tabella hash.
+1

La prima volta che ho visto una subquery correlata che in realtà doveva essere una subquery correlata che avrei potuto eliminare in meno di 5 minuti. Vorrei aver conosciuto questo trucco * anni * fa. –

+0

'@Philip Kelley': quale trucco esattamente? – Quassnoi

+0

Cosa intendi in questa sezione: "Questo è utile per SQL Server se vuoi controllare due o più colonne, poiché SQL Server non supporta questa sintassi:". Stai dicendo che questo non si applica a SQL Server? Ti manca un "non"? – Stimy

1

io non sono sicuro di quello che è il più efficiente, ma altre opzioni sono:

1. Use EXISTS 

SELECT * 
FROM ORDERS O 
WHERE NOT EXISTS (SELECT 1 
        FROM HeldOrders HO 
        WHERE O.Order_ID = HO.OrderID) 

2. Use EXCEPT 

SELECT O.Order_ID 
FROM ORDERS O 
EXCEPT 
SELECT HO.Order_ID 
FROM HeldOrders 
0

Prova

SELECT * 
FROM Orders 
LEFT JOIN HeldOrders 
ON HeldOrders.Order_ID = Orders.Order_ID 
WHERE HeldOrders.Order_ID IS NULL 
Problemi correlati