Come selezionare le righe senza voce corrispondente in un'altra tabella?


323

Sto facendo alcuni lavori di manutenzione su un'applicazione di database e ho scoperto che, gioia delle gioie, anche se i valori di una tabella vengono utilizzati nello stile di chiavi esterne, non ci sono vincoli di chiave esterna sulle tabelle.

Sto cercando di aggiungere vincoli FK su queste colonne, ma sto scoprendo che, poiché ci sono già un sacco di dati errati nelle tabelle da errori precedenti che sono stati corretti ingenuamente, ho bisogno di trovare le righe che non lo fanno abbinare l'altra tabella e quindi eliminarli.

Ho trovato alcuni esempi di questo tipo di query sul Web, ma sembrano tutti fornire esempi anziché spiegazioni e non capisco perché funzionino.

Qualcuno può spiegarmi come costruire una query che restituisca tutte le righe senza corrispondenze in un'altra tabella e cosa sta facendo, in modo che io possa fare queste query da solo, piuttosto che arrivare a SO per ogni tabella in questo pasticcio che ha nessun vincolo FK?

Risposte:


614

Ecco una semplice query:

SELECT t1.ID
FROM Table1 t1
    LEFT JOIN Table2 t2 ON t1.ID = t2.ID
WHERE t2.ID IS NULL

I punti chiave sono:

  1. LEFT JOINviene usato; questo restituirà TUTTE le righe Table1, indipendentemente dal fatto che ci sia o meno una riga corrispondente in Table2.

  2. La WHERE t2.ID IS NULLclausola; questo limiterà i risultati restituiti solo a quelle righe in cui l'ID restituito Table2è nullo, in altre parole non è presente alcun record Table2per quell'ID specifico da Table1. Table2.IDverrà restituito come NULL per tutti i record da Table1cui l'ID non corrisponde Table2.


4
Non riesce se un ID è NULL
Michael

169
@Michael - Se avere un NULLID è valido nel tuo schema, potresti avere problemi più grandi, non saresti d'accordo? :)
rinogo,

1
funzionerà anche se table1 ha più record di table2? se table1 ha 100 record e table2 ha 200 record (100 che corrispondono / uniscono e 100 che non corrispondono / uniscono) verrebbero restituiti tutti i 200 record?
Juan Velez,

1
Spesso mi piace avvolgere il join sinistro come una subquery / vista in linea al fine di garantire che non vi sia interazione tra la clausola WHERE e LEFT JOIN.
Andrew Wolfe,

1
@Jas Punto chiave 1 della risposta, TUTTE le righe della prima tabella, anche quelle che non corrispondono a t1.ID = condizione t2.ID del join sinistro. Se cambi la prima riga in SELECT t1.ID, t2.IDe rimuovi la riga WHERE avrai un'idea migliore di come funziona.
Peter Laboš,

97

Vorrei usare l' EXISTSespressione poiché è più potente, puoi cioè scegliere più precisamente le righe a cui desideri unirti, nel caso in cui LEFT JOINdevi prendere tutto ciò che è nella tabella unita. La sua efficienza è probabilmente uguale a quella del LEFT JOINtest null.

SELECT t1.ID
FROM Table1 t1
WHERE NOT EXISTS (SELECT t2.ID FROM Table2 t2 WHERE t1.ID = t2.ID)

Qualcosa di così semplice viene facilmente gestito da Query Optimizer per la migliore esecuzione.
Andrew Wolfe,

2
Sì, il vantaggio principale EXISTSè la sua variabilità.
Ondrej Bozek,

1
Semplice, elegante e ha risolto il mio problema! Ben fatto!
Mike Potente,

2
In realtà ridotto la velocità di una query che ho avuto da 7 sec a 200ms ... (rispetto a WHERE t2.id IS NULL) Grazie.
Moti Korets,

4
@MotiKorets intendi aumentare la velocità :)
Ondrej Bozek il

14
SELECT id FROM table1 WHERE foreign_key_id_column NOT IN (SELECT id FROM table2)

La tabella 1 contiene una colonna alla quale si desidera aggiungere il vincolo di chiave esterna, ma i valori in foreign_key_id_columnnon tutti corrispondono a quelli idnella tabella 2.

  1. La selezione iniziale elenca le ids dalla tabella1. Queste saranno le righe che vogliamo eliminare.
  2. La NOT INclausola nell'istruzione where limita la query alle sole righe in cui il valore in foreign_key_id_columnnon è nell'elenco delle tabelle 2 ids.
  3. L' SELECTistruzione tra parentesi otterrà un elenco di tutti gli ids presenti nella tabella 2.

@ zb226: il tuo link a ha a che fare con i limiti della INclausola con un elenco di valori letterali. Non si applica all'uso di una INclausola con il risultato di una sottoquery. Quella risposta accettata a quella domanda risolve effettivamente il problema usando una sottoquery. (Un ampio elenco di valori letterali è problematico perché crea un'enorme espressione SQL. Una sottoquery funziona bene perché, anche se l'elenco risultante è grande, l'espressione SQL stessa è piccola.)
Kannan Goundan

@KannanGoundan Hai perfettamente ragione. Ritirare il commento imperfetto.
zb226,

8

Dov'è T2la tabella a cui stai aggiungendo il vincolo:

SELECT *
FROM T2
WHERE constrained_field NOT
IN (
    SELECT DISTINCT t.constrained_field
    FROM T2 
    INNER JOIN T1 t
    USING ( constrained_field )
)

Ed elimina i risultati.


4

Lascia che abbiamo le seguenti 2 tabelle (salario e dipendente) inserisci qui la descrizione dell'immagine

Ora voglio quei record dalla tabella dei dipendenti che non sono in stipendio. Possiamo farlo in 3 modi:

  1. Utilizzando Join interno
select * from employee
where id not in(select e.id from employee e inner join salary s on e.id=s.id)

inserisci qui la descrizione dell'immagine

  1. Utilizzo del join esterno sinistro
select * from employee e 
left outer join salary s on e.id=s.id  where s.id is null

inserisci qui la descrizione dell'immagine

  1. Utilizzando Full Join
select * from employee e
full outer join salary s on e.id=s.id where e.id not in(select id from salary)

inserisci qui la descrizione dell'immagine



0

Puoi optare per Views come mostrato di seguito:

CREATE VIEW AuthorizedUserProjectView AS select t1.username as username, t1.email as useremail, p.id as projectid, 
(select m.role from userproject m where m.projectid = p.id and m.userid = t1.id) as role 
FROM authorizeduser as t1, project as p

e quindi lavorare sulla vista per selezionare o aggiornare:

select * from AuthorizedUserProjectView where projectid = 49

che produce il risultato come mostrato nella figura seguente, ovvero per la colonna non corrispondente è stato inserito il valore null.

[Result of select on the view][1]

0

Non sapevo quale sia ottimizzato (rispetto a @AdaTheDev) ma questo sembra essere più veloce quando uso (almeno per me)

SELECT id  FROM  table_1 EXCEPT SELECT DISTINCT (table1_id) table1_id FROM table_2

Se vuoi ottenere qualsiasi altro attributo specifico puoi usare:

SELECT COUNT(*) FROM table_1 where id in (SELECT id  FROM  table_1 EXCEPT SELECT DISTINCT (table1_id) table1_id FROM table_2);


-2

Puoi fare qualcosa del genere

   SELECT IFNULL(`price`.`fPrice`,100) as fPrice,product.ProductId,ProductName 
          FROM `products` left join `price` ON 
          price.ProductId=product.ProductId AND (GeoFancingId=1 OR GeoFancingId 
          IS NULL) WHERE Status="Active" AND Delete="No"

-6

Come selezionare le righe senza voce corrispondente in entrambe le tabelle?

    selezionare * da [dbo]. [EmppDetails] e
     unisciti a destra [Impiegato]. [Sesso] d su e.Gid = d.Gid
    dove e.Gid è Null

    unione 
    selezionare * da [dbo]. [EmppDetails] e
     left join [Employee]. [Gender] d on e.Gid = d.Gid
    dove d.Gid è Null

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.