2011-01-12 8 views
5

Sto cercando un modo per filtrare le righe da un SELECT di una tabella in base a determinati valori nelle righe di un'altra tabella.rimozione di righe da un SELECT in base a colonne in un'altra tabella

Sto sperimentando la seguente struttura di esempio. Ho una tabella dei contenuti dei post del blog (una riga per post del blog) e un'altra tabella dei metadati sui post (una riga per coppia chiave-valore; ogni riga con una colonna che la associa a un post del blog; post sul blog). Voglio tirare una riga di posts solo se non ci sono righe in metadata dove metadata.pid=posts.pid AND metadata.k='optout'. Cioè, per la struttura di esempio qui sotto, voglio solo recuperare la riga posts.id=1.

(sulla base di quello che ho provato) JOIN s non finiscono di rimuovere i posti che hanno alcuni metadati dove metadata.k='optout', perché l'altra fila di metadati per quel pid significa che rende nei risultati.

mysql> select * from posts; 
+-----+-------+--------------+ 
| pid | title | content  | 
+-----+-------+--------------+ 
| 1 | Foo | Some content | 
| 2 | Bar | More content | 
| 3 | Baz | Something | 
+-----+-------+--------------+ 
3 rows in set (0.00 sec) 

mysql> select * from metadata; 
+------+-----+--------+-----------+ 
| mdid | pid | k  | v   | 
+------+-----+--------+-----------+ 
| 1 | 1 | date | yesterday | 
| 2 | 1 | thumb | img.jpg | 
| 3 | 2 | date | today  | 
| 4 | 2 | optout | true  | 
| 5 | 3 | date | tomorrow | 
| 6 | 3 | optout | true  | 
+------+-----+--------+-----------+ 
6 rows in set (0.00 sec) 

Una subquery mi può dare l'inverso di ciò che voglio:

mysql> select posts.* from posts where pid = any (select pid from metadata where k = 'optout'); 
+-----+-------+--------------+ 
| pid | title | content  | 
+-----+-------+--------------+ 
| 2 | Bar | More content | 
| 3 | Baz | Something | 
+-----+-------+--------------+ 
2 rows in set (0.00 sec) 

... ma utilizzando pid != any (...) mi dà tutti e 3 le righe di messaggi, causare ogni singolo pid ha una fila di metadati dove k!='optout'.

risposta

8

Suoni come si desidera eseguire un LEFT JOIN e quindi verificare i risultati in cui il valore della tabella unita è NULL, ad indicare che non esiste alcun record unito.

Ad esempio:

SELECT * FROM posts 
LEFT JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = 'optout') 
WHERE metadata.mdid IS NULL; 

Questo selezionerà ogni riga della tabella posts per cui non esiste alcuna corrispondenza metadata riga con un valore di k = 'optout'.

modifica: vale la pena notare che questa è una proprietà chiave di un join di sinistra e non funzionerebbe con un join regolare; un join sinistro restituirà sempre i valori dalla prima tabella, anche se non esistono valori corrispondenti nelle tabelle unite, consentendo di eseguire selezioni in base all'assenza di tali righe.

Edit 2: Chiariamo cosa sta succedendo qui rispetto al LEFT JOIN rispetto al JOIN (che mi riferisco a come INNER JOIN per chiarezza, ma è intercambiabili in MySQL).

Supponiamo che si esegue una di queste due domande:

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
INNER JOIN metadata ON posts.pid = metadata.pid; 

o

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
LEFT JOIN metadata ON posts.pid = metadata.pid; 

entrambe le query produrre il set di risultati seguente:

+-----+-------+--------------+------+-------+-----------+ 
| pid | title | content  | mdid | k  | v   | 
+-----+-------+--------------+------+-------+-----------+ 
| 1 | Foo | Some content | 1 | date | yesterday | 
| 1 | Foo | Some content | 2 | thumb | img.jpg | 
+-----+-------+--------------+------+-------+-----------+ 

Ora, supponiamo modifichiamo il query per aggiungere i criteri extra per "optout" che è stato menzionato.In primo luogo, il INNER JOIN:

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
INNER JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = "optout"); 

Come previsto, questo ha prodotto alcun risultato:

Empty set (0.00 sec) 

Ora, cambiando che ad un LEFT JOIN:

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
FROM posts 
LEFT JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = "optout"); 

Ciò produce un set di risultati:

+-----+-------+--------------+------+------+------+ 
| pid | title | content  | mdid | k | v | 
+-----+-------+--------------+------+------+------+ 
| 1 | Foo | Some content | NULL | NULL | NULL | 
+-----+-------+--------------+------+------+------+ 

La differenza tra un INNER JOIN e un è che un INNER JOIN restituirà un risultato solo se le righe di ENTRAMBI le tabelle unite corrispondono. In un LEFT JOIN, le righe corrispondenti della prima tabella verranno SEMPRE restituite, indipendentemente dal fatto che venga trovato qualcosa a cui partecipare. In molti casi non importa quale si utilizza, ma è importante scegliere quello giusto in modo da non ottenere risultati imprevisti su tutta la linea.

Quindi, in questo caso, la query suggerito di:

SELECT posts.*, metadata.mdid, metadata.k, metadata.v 
LEFT JOIN metadata ON (posts.pid = metadata.pid AND metadata.k = 'optout') 
WHERE metadata.mdid IS NULL; 

restituirà lo stesso set di risultati di cui sopra:

+-----+-------+--------------+------+------+------+ 
| pid | title | content  | mdid | k | v | 
+-----+-------+--------------+------+------+------+ 
| 1 | Foo | Some content | NULL | NULL | NULL | 
+-----+-------+--------------+------+------+------+ 

Speriamo che libera in su! Le unioni sono una grande cosa da imparare, avendo una piena comprensione di quando usare quale è una cosa molto buona.

+0

Quindi fammi vedere se ottengo questo ... per un posto con optout, la subquery corrisponde una fila di metadati, in modo che il metadata.mdid non è nullo, così non viene selezionato. Ma un post senza optout, la sottoquery non corrisponde a una riga, quindi il lato destro viene riempito con valori null, quindi la clausola where è true. – alxndr

+1

Ho aggiunto un'altra sezione alla risposta relativa a come funziona il join, che dovrebbe chiarire qualsiasi area grigia con esso. Spero possa aiutare! – futureal

3

Si può provare qualcosa di simile

select p.* 
from posts p 
where NOT EXISTS (
         select pid 
         from metadata 
         where k = 'optout' 
         and  pid = p.pid 
        ) 
+0

Wow, grazie. Dovresti leggere su NOT EXISTS. – alxndr

+0

FYI dando il segno di spunta all'altra causa causa su 36000 righe il join di sinistra è. 1 sec più veloce ... – alxndr

+1

Su un piccolo risultato impostare le due query dovrebbero eseguire circa lo stesso. Tuttavia, quando si usa 'EXISTS' o' NOT EXISTS' con una sottoquery, sarà necessario calcolare la tabella dei sottoquery e copiarla su una tabella temporanea. Così come crescono i tuoi set di risultati, potrebbe diventare un collo di bottiglia di grandi prestazioni. Non vorrei evitarlo del tutto, a volte rende le cose molto più facili da leggere/comprendere rispetto a un join complicato: devi solo essere consapevole del tipo di set di risultati che potrebbe eventualmente produrre. – futureal

Problemi correlati