Partecipa vs. query secondaria


837

Sono un utente MySQL di vecchia scuola e ho sempre preferito JOINuna query secondaria. Ma al giorno d'oggi tutti usano una query secondaria e io la odio; Non so perché.

Mi mancano le conoscenze teoriche per giudicare da solo se c'è qualche differenza. Una sottoquery è valida come una JOINe quindi non c'è nulla di cui preoccuparsi?


23
Le subquery sono eccezionali a volte. Ottimizzano le prestazioni in MySQL. Non usarli.
runrig,

8
Ho sempre avuto l'impressione che le sottoquery implicitamente fossero eseguite come join laddove disponibili in alcune tecnologie DB.
Kezzer,

18
Le query secondarie non sempre fanno schifo, quando si uniscono a tabelle piuttosto grandi, il modo preferito è fare una sub-selezione da quella tabella grande (limitando il numero di righe) e quindi unendo.
ovais.tariq,

136
"oggigiorno tutti usano una query secondaria" [citazione necessaria]
Piskvor ha lasciato l'edificio il

3
Potenzialmente correlate (anche se molto più specifico): stackoverflow.com/questions/141278/subqueries-vs-joins/...
Leigh Brenecki

Risposte:


191

Tratto dal manuale di MySQL ( 13.2.10.11 Riscrittura delle sottoquery come join ):

Un JOIN LEFT [OUTER] può essere più veloce di una subquery equivalente perché il server potrebbe essere in grado di ottimizzarlo meglio, un fatto che non è specifico solo per MySQL Server.

Quindi le subquery possono essere più lente di LEFT [OUTER] JOIN, ma secondo me la loro forza è una leggibilità leggermente più alta.


45
@ user1735921 IMO dipende ... In generale, è molto importante la leggibilità del codice, perché è di grande importanza per la sua successiva gestione ... Ricordiamo la famosa affermazione di Donald Knuth: "L'ottimizzazione prematura è la radice di tutto il male (o almeno la maggior parte) nella programmazione " . Tuttavia, naturalmente ci sono aree di programmazione in cui le prestazioni sono fondamentali ... Idealmente, quando si riesce a riconciliarsi con l'altro :)
simhumileco,

31
Nelle query più complesse, trovo che i join siano molto più facili da leggere rispetto alle query secondarie. le sottoquery si trasformano in una scodella di noodles nella mia testa.
Zahra,

6
@utente1735921 certo, specialmente quando la query diventa così complicata che fa la cosa sbagliata e passi una giornata a sistemarla ... c'è un equilibrio tra, come al solito.
fabio.sussetto,

6
@ user1735921 Solo se i miglioramenti delle prestazioni valgono l'aumento dei tempi di manutenzione richiesti in futuro
Joshua Schlichting,

3
La mia opinione Joine sub queryha una sintassi diversa, quindi la leggibilità che non possiamo confrontare, entrambi hanno una maggiore leggibilità fintanto che sei bravo nella sintassi SQL. Le prestazioni sono più importanti.
Thavaprakash Swaminathan,

842

Le sottoquery sono il modo logicamente corretto per risolvere i problemi del modulo, "Ottieni fatti da A, subordinatamente a fatti da B". In tali casi, ha più senso logico incollare B in una sottoquery piuttosto che eseguire un join. È anche più sicuro, in senso pratico, dal momento che non devi essere cauto nel ricevere fatti duplicati da A a causa di partite multiple contro B.

In pratica, tuttavia, la risposta di solito si riduce alle prestazioni. Alcuni ottimizzatori succhiano i limoni quando viene assegnato un join rispetto a una query secondaria e altri succhiano i limoni dall'altra parte, e questo è specifico dell'ottimizzatore, specifico della versione DBMS e specifico della query.

Storicamente, i join espliciti di solito vincono, quindi la saggezza consolidata che i join sono migliori, ma gli ottimizzatori stanno migliorando continuamente, quindi preferisco scrivere prima le query in modo logicamente coerente, e quindi ristrutturare se i vincoli di prestazione lo giustificano.


105
Bella risposta. Aggiungo anche che gli sviluppatori (specialmente quelli amatoriali) non sono sempre competenti in SQL.
Álvaro González,

4
+1 Alla ricerca di una spiegazione logica per questo problema da molto tempo, questa è l'unica risposta che mi sembra logica
Ali Umair,

1
@Marcelo Cantos, potresti per favore fare un esempio della tua affermazione "È anche più sicuro, in senso pratico, dal momento che non devi essere cauto nel ricevere fatti duplicati da A a causa di più partite contro B."? L'ho trovato molto penetrante ma un po 'troppo astratto. Grazie.
Jinghui Niu,

6
@JinghuiNiu I clienti che hanno acquistato oggetti costosi: select custid from cust join bought using (custid) where price > 500. Se un cliente ha acquistato più articoli costosi, otterrai raddoppi. Per risolvere questo problema, select custid from cust where exists (select * from bought where custid = cust.custid and price > 500). È possibile utilizzare select distinct …invece, ma spesso è più lavoro, sia per l'ottimizzatore che per il valutatore.
Marcelo Cantos,

1
@MatTheWhale sì, ho usato una risposta semplificata perché ero pigro. In uno scenario reale, tireresti più colonne di un semplice custid dalla cust.
Marcelo Cantos,

357

Nella maggior parte dei casi JOINi messaggi sono più veloci delle query secondarie ed è molto raro che una query secondaria sia più veloce.

In JOINRDBMS puoi creare un piano di esecuzione migliore per la tua query e in grado di prevedere quali dati devono essere caricati per essere elaborati e risparmiare tempo, a differenza della sottoquery in cui eseguirà tutte le query e caricherà tutti i loro dati per eseguire l'elaborazione .

La cosa buona nelle sottoquery è che sono più leggibili di JOINs: ecco perché la maggior parte delle nuove persone SQL le preferisce; è il modo semplice; ma quando si tratta di prestazioni, i JOIN sono migliori nella maggior parte dei casi anche se non sono difficili da leggere.


14
Sì, la maggior parte dei database lo include quindi come passaggio di ottimizzazione per convertire le sottoquery in join durante l'analisi della query.
Cine

16
Questa risposta è un po 'troppo semplificata per la domanda che è stata posta. Come dici tu: alcune sottoquery sono ok e alcune no. La risposta non aiuta davvero a distinguere i due. (anche il "molto raro" dipende davvero dai tuoi dati / app).
Unreason,

21
puoi provare qualcuno dei tuoi punti con riferimento alla documentazione o risultati del test?
Uğur Gümüşhan,

62
Ho fatto ottime esperienze con le sottoquery che contengono un riferimento alla query superiore, specialmente quando si tratta di conteggi di righe superiori a 100.000. La cosa sembra essere l'utilizzo della memoria e il paging del file di scambio. Un join produrrebbe una grande quantità di dati, che potrebbero non rientrare nella memoria e che devono essere inseriti nel file di scambio. Ogni volta che questo è il caso, i tempi di interrogazione di piccoli sotto-selezioni simili select * from a where a.x = (select b.x form b where b.id = a.id)sono estremamente ridotti rispetto a un join. Questo è un problema molto specifico, ma in alcuni casi ti porta da ore a minuti.
zuloo,

13
Ho esperienza con Oracle e posso dire che le query secondarie sono molto migliori su tabelle di grandi dimensioni se non si dispone di alcun filtro o ordinamento su di esse.
Amir Pashazadeh,

130

Utilizzare EXPLAIN per vedere come il database esegue la query sui dati. C'è un enorme "dipende" in questa risposta ...

PostgreSQL può riscrivere una sottoquery su un join o un join su una subquery quando pensa che uno sia più veloce dell'altro. Tutto dipende da dati, indici, correlazione, quantità di dati, query, ecc.


6
questo è esattamente il motivo per cui postgresql è così buono e utile che capisce qual è l'obiettivo e risolverà una query in base a ciò che ritiene migliore e postgresql è molto bravo a sapere come guardare i suoi dati
WojonsTech,

heww. Immagino non sia necessario riscrivere tonnellate di domande per me! postgresql per la vittoria.
Daniel Shin,

77

Nel 2010 mi sarei unito all'autore di queste domande e avrei votato con forza JOIN, ma con molta più esperienza (specialmente in MySQL) posso affermare: Sì, le sottoquery possono essere migliori. Ho letto più risposte qui; alcune sottoquery dichiarate sono più veloci, ma mancava di una buona spiegazione. Spero di poter fornire una risposta (molto) tardiva:

Prima di tutto, lasciami dire il più importante: esistono diverse forme di sottoquery

E la seconda importante affermazione: le dimensioni contano

Se si utilizzano query secondarie, è necessario essere consapevoli di come DB-Server esegue la query secondaria. Soprattutto se la sottoquery viene valutata una volta o per ogni riga! Dall'altro lato, un moderno DB-Server è in grado di ottimizzare molto. In alcuni casi una sottoquery aiuta a ottimizzare una query, ma una versione più recente del DB-Server potrebbe rendere obsoleta l'ottimizzazione.

Sottoquery in Select-Fields

SELECT moo, (SELECT roger FROM wilco WHERE moo = me) AS bar FROM foo

Tenere presente che viene eseguita una query secondaria per ogni riga risultante da foo.
Evitatelo se possibile; potrebbe rallentare drasticamente la query su enormi set di dati. Tuttavia, se la query secondaria non ha riferimenti ad fooessa, può essere ottimizzata dal server DB come contenuto statico e può essere valutata una sola volta.

Sottoquery nell'istruzione Where

SELECT moo FROM foo WHERE bar = (SELECT roger FROM wilco WHERE moo = me)

Se sei fortunato, il DB lo ottimizza internamente in a JOIN. Altrimenti, la tua query diventerà molto, molto lenta su enormi set di dati perché eseguirà la sottoquery per ogni riga in foo, non solo i risultati come nel tipo selezionato.

Sottoquery nell'istruzione Join

SELECT moo, bar 
  FROM foo 
    LEFT JOIN (
      SELECT MIN(bar), me FROM wilco GROUP BY me
    ) ON moo = me

Questo è interessante. Combiniamo JOINcon una sottoquery. E qui otteniamo la vera forza delle sottoquery. Immagina un set di dati con milioni di righe wilcoma solo alcune distinte me. Invece di unirci contro un tavolo enorme, ora abbiamo un tavolo temporaneo più piccolo a cui unirci. Ciò può comportare query molto più veloci a seconda delle dimensioni del database. Puoi avere lo stesso effetto con CREATE TEMPORARY TABLE ...e INSERT INTO ... SELECT ..., che potrebbe fornire una migliore leggibilità su query molto complesse (ma può bloccare i set di dati in un livello di isolamento di lettura ripetibile).

Sottoquery annidate

SELECT moo, bar
  FROM (
    SELECT moo, CONCAT(roger, wilco) AS bar
      FROM foo
      GROUP BY moo
      HAVING bar LIKE 'SpaceQ%'
  ) AS temp_foo
  ORDER BY bar

È possibile nidificare sottoquery in più livelli. Questo può aiutare su enormi set di dati se devi raggruppare o ordinare i risultati. Di solito il DB-Server crea una tabella temporanea per questo, ma a volte non è necessario l'ordinamento sull'intera tabella, solo sul set di risultati. Ciò potrebbe fornire prestazioni molto migliori a seconda delle dimensioni della tabella.

Conclusione

Le sottoquery non sostituiscono a JOINe non dovresti usarle in questo modo (sebbene possibile). A mio modesto parere, l'uso corretto di una sottoquery è l'uso come una rapida sostituzione di CREATE TEMPORARY TABLE .... Una buona query secondaria riduce un set di dati in un modo che non è possibile realizzare in una ONdichiarazione di a JOIN. Se una sottoquery ha una delle parole chiave GROUP BYo DISTINCTe preferibilmente non si trova nei campi selezionati o nell'istruzione where, potrebbe migliorare molto le prestazioni.


3
Per Sub-queries in the Join-statement: (1) la generazione della tabella derivata dalla query secondaria stessa potrebbe richiedere molto tempo. (2) la tabella derivata risultante non è indicizzata. questi due soli potrebbero rallentare significativamente l'SQL.
jxc,

@jxc Posso parlare solo per MySQL (1) C'è una tabella temporanea simile a un join. Il tempo dipende dalla quantità di dati. Se non è possibile ridurre i dati con una sottoquery, utilizzare un join. (2) Esatto, dipende dal fattore in cui è possibile ridurre i dati nella tabella temporanea. Ho avuto casi reali, in cui ho potuto ridurre la dimensione del join da qualche milione a qualche centinaio e ridurre il tempo di interrogazione da più secondi (con utilizzo completo dell'indice) a un quarto di secondo con una sottoquery.
Trendfischer,

IMO: (1) tale tabella temporanea (tabella derivata) non si materializza, quindi ogni volta che si esegue l'SQL, è necessario ricreare la tabella temporanea, che potrebbe essere molto costosa e un vero collo di bottiglia (vale a dire far funzionare un gruppo da milioni of records) (2) anche se è possibile ridurre la dimensione della tabella temporanea ai 10record, poiché non esiste un indice, ciò significa potenzialmente potenzialmente interrogare 9 volte più record di dati rispetto alla tabella temporanea quando si UNISCONO ad altre tabelle. A proposito ho avuto questo problema in precedenza con il mio db (MySQL), nel mio caso, l'uso di sub-query in SELECT listpotrebbe essere molto più veloce.
jxc,

@jxc Non dubito che ci siano molti esempi, in cui l'utilizzo di una subquery è meno ottimale. Come buona pratica, è necessario utilizzare EXPLAINuna query prima dell'ottimizzazione. Con il vecchio set profiling=1si poteva facilmente vedere, se una tabella temporanea è un collo di bottiglia. E anche un indice richiede tempo di elaborazione, B-Trees ottimizza le query per i record, ma una tabella di 10 record può essere molto più veloce di un indice per milioni di record. Ma dipende da molteplici fattori come dimensioni e tipi di campo.
Trendfischer,

1
Mi è davvero piaciuta la tua spiegazione. Grazie.
unpairestgood

43

Prima di tutto, per confrontare i due prima devi distinguere le query con le subquery con:

  1. una classe di sottoquery che hanno sempre una query equivalente corrispondente scritta con join
  2. una classe di sottoquery che non possono essere riscritte usando i join

Per la prima classe di query un buon RDBMS vedrà i join e le subquery come equivalenti e produrrà gli stessi piani di query.

In questi giorni anche mysql lo fa.

Tuttavia, a volte non lo è, ma ciò non significa che i join vinceranno sempre: ho avuto casi durante l'utilizzo di subquery in mysql prestazioni migliorate. (Ad esempio, se esiste qualcosa che impedisce al planner mysql di stimare correttamente il costo e se il planner non vede la variante join e la variante subquery uguali, le subquery possono superare i join forzando un determinato percorso).

La conclusione è che dovresti testare le tue query sia per le varianti di join che per quelle di subquery se vuoi essere sicuro di quale si esibirà meglio.

Per la seconda classe il confronto non ha senso in quanto tali query non possono essere riscritte utilizzando i join e in questi casi le query secondarie sono un modo naturale per eseguire le attività richieste e non è necessario discriminarle.


1
puoi fornire un esempio di una query scritta usando sottoquery che non possono essere convertite in join (seconda classe, come la chiami)?
Zahra,

24

Penso che ciò che è stato sottovalutato nelle risposte citate sia il problema dei duplicati e dei risultati problematici che possono derivare da casi specifici (d'uso).

(anche se Marcelo Cantos lo menziona)

Citerò l'esempio dei corsi Lagunita di Stanford su SQL.

Tavolo dello studente

+------+--------+------+--------+
| sID  | sName  | GPA  | sizeHS |
+------+--------+------+--------+
|  123 | Amy    |  3.9 |   1000 |
|  234 | Bob    |  3.6 |   1500 |
|  345 | Craig  |  3.5 |    500 |
|  456 | Doris  |  3.9 |   1000 |
|  567 | Edward |  2.9 |   2000 |
|  678 | Fay    |  3.8 |    200 |
|  789 | Gary   |  3.4 |    800 |
|  987 | Helen  |  3.7 |    800 |
|  876 | Irene  |  3.9 |    400 |
|  765 | Jay    |  2.9 |   1500 |
|  654 | Amy    |  3.9 |   1000 |
|  543 | Craig  |  3.4 |   2000 |
+------+--------+------+--------+

Applica tabella

(domande presentate a università e major specifiche)

+------+----------+----------------+----------+
| sID  | cName    | major          | decision |
+------+----------+----------------+----------+
|  123 | Stanford | CS             | Y        |
|  123 | Stanford | EE             | N        |
|  123 | Berkeley | CS             | Y        |
|  123 | Cornell  | EE             | Y        |
|  234 | Berkeley | biology        | N        |
|  345 | MIT      | bioengineering | Y        |
|  345 | Cornell  | bioengineering | N        |
|  345 | Cornell  | CS             | Y        |
|  345 | Cornell  | EE             | N        |
|  678 | Stanford | history        | Y        |
|  987 | Stanford | CS             | Y        |
|  987 | Berkeley | CS             | Y        |
|  876 | Stanford | CS             | N        |
|  876 | MIT      | biology        | Y        |
|  876 | MIT      | marine biology | N        |
|  765 | Stanford | history        | Y        |
|  765 | Cornell  | history        | N        |
|  765 | Cornell  | psychology     | Y        |
|  543 | MIT      | CS             | N        |
+------+----------+----------------+----------+

Proviamo a trovare i punteggi GPA per gli studenti che hanno presentato domanda di CSspecializzazione (indipendentemente dall'università)

Utilizzando una sottoquery:

select GPA from Student where sID in (select sID from Apply where major = 'CS');

+------+
| GPA  |
+------+
|  3.9 |
|  3.5 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

Il valore medio per questo set di risultati è:

select avg(GPA) from Student where sID in (select sID from Apply where major = 'CS');

+--------------------+
| avg(GPA)           |
+--------------------+
| 3.6800000000000006 |
+--------------------+

Utilizzando un join:

select GPA from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+------+
| GPA  |
+------+
|  3.9 |
|  3.9 |
|  3.5 |
|  3.7 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

valore medio per questo set di risultati:

select avg(GPA) from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+-------------------+
| avg(GPA)          |
+-------------------+
| 3.714285714285714 |
+-------------------+

È ovvio che il secondo tentativo produce risultati fuorvianti nel nostro caso d'uso, dato che conta i duplicati per il calcolo del valore medio. È anche evidente che l'uso di distinctcon l'istruzione basata su join non eliminerà il problema, dato che manterrà erroneamente una delle tre occorrenze del 3.9punteggio. Il caso corretto è tenere conto di DUE (2) occorrenze del 3.9punteggio dato che in realtà abbiamo DUE (2) studenti con quel punteggio conforme ai nostri criteri di ricerca.

Sembra che in alcuni casi una sottoquery sia il modo più sicuro di procedere, oltre a qualsiasi problema di prestazioni.


Penso che non sia possibile utilizzare una query secondaria qui. Questo non è un caso in cui sia logicamente possibile utilizzare uno, ma uno dà una risposta sbagliata a causa della sua implementazione tecnica. Questo è un caso in cui NON PUOI usare una sottoquery perché uno studente non appartenente a CS può ottenere un punteggio di 3,9 che è nella lista IN dei punteggi. Il contesto di CS viene perso una volta eseguita la query secondaria, che non è quello che vogliamo logicamente. Quindi questo non è un buon esempio in cui entrambi possono essere usati. L'utilizzo della sottoquery è concettualmente / logicamente errato per questo caso d'uso anche se fortunatamente dà il risultato giusto per un set di dati diverso.
Saurabh Patil,

22

La documentazione MSDN per SQL Server dice

Molte istruzioni Transact-SQL che includono sottoquery possono essere in alternativa formulate come join. Altre domande possono essere poste solo con le subquery. In Transact-SQL, di solito non esiste alcuna differenza di prestazioni tra un'istruzione che include una sottoquery e una versione semanticamente equivalente che non lo fa. Tuttavia, in alcuni casi in cui l'esistenza deve essere verificata, un join produce prestazioni migliori. Altrimenti, la query nidificata deve essere elaborata per ogni risultato della query esterna per garantire l'eliminazione dei duplicati. In tali casi, un approccio congiunto produrrebbe risultati migliori.

quindi se hai bisogno di qualcosa del genere

select * from t1 where exists select * from t2 where t2.parent=t1.id

prova invece a utilizzare join. In altri casi, non fa differenza.

Dico: la creazione di funzioni per le sottoquery elimina il problema del disordine e consente di implementare una logica aggiuntiva per le sottoquery. Quindi consiglio di creare funzioni per le subquery quando possibile.

Il disordine nel codice è un grosso problema e l'industria ha lavorato per evitarlo per decenni.


9
Sostituire le sottoquery con le funzioni è una pessima idea dal punto di vista delle prestazioni in alcuni RDBMS (ad es. Oracle), quindi raccomanderei esattamente il contrario: utilizzare sottoquery / join invece di funzioni laddove possibile.
Frank Schmitt,

3
@FrankSchmitt si prega di supportare la tua argomentazione con riferimenti.
Uğur Gümüşhan,

2
Ci sono anche casi in cui dovresti usare una sotto query invece di un join anche se controlli l'esistenza: se controlli NOT EXISTS. A NOT EXISTSvince su a LEFT OUTER JOIN per vari motivi: preformance, fail-safety (in caso di colonne annullabili) e leggibilità. sqlperformance.com/2012/12/t-sql-queries/left-anti-semi-join
Tim Schmelter

16

Esegui su un database molto grande da un vecchio CMS Mambo:

SELECT id, alias
FROM
  mos_categories
WHERE
  id IN (
    SELECT
      DISTINCT catid
    FROM mos_content
  );

0 secondi

SELECT
  DISTINCT mos_content.catid,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

~ 3 secondi

Un EXPLAIN mostra di esaminare esattamente lo stesso numero di righe, ma uno impiega 3 secondi e l'altro è quasi istantaneo. Morale della storia? Se le prestazioni sono importanti (quando non lo sono?), Provale in diversi modi e vedi quale è la più veloce.

E...

SELECT
  DISTINCT mos_categories.id,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

0 secondi

Ancora una volta, stessi risultati, stesso numero di righe esaminate. La mia ipotesi è che DISTINCT mos_content.catid impieghi molto più tempo a capire di DISTINCT mos_categories.id.


1
Mi piacerebbe sapere di più su ciò che stai cercando di sottolineare nell'ultima riga "La mia ipotesi è che DISTINCT mos_content.catid impiega molto più tempo a capire di DISTINCT mos_categories.id." . Stai dicendo che un ID dovrebbe essere chiamato solo ide non come qualcosa del genere catid? Cercando di ottimizzare gli accessi al mio db e i tuoi apprendimenti potrebbero aiutarti.
bool.dev,

2
usare SQL IN in quel caso è una cattiva pratica e non dimostra nulla.
Uğur Gümüşhan,

15

Secondo la mia osservazione, come in due casi, se una tabella ha meno di 100.000 record, il join funzionerà rapidamente.

Ma nel caso in cui una tabella contenga più di 100.000 record, il risultato migliore è una sottoquery.

Ho una tabella che ha 500.000 record che ho creato sotto query e il suo tempo di risultato è simile

SELECT * 
FROM crv.workorder_details wd 
inner join  crv.workorder wr on wr.workorder_id = wd.workorder_id;

Risultato: 13,3 secondi

select * 
from crv.workorder_details 
where workorder_id in (select workorder_id from crv.workorder)

Risultato: 1,65 secondi


Sono d'accordo, a volte anche interrompere la query funziona, quando hai milioni di record, non vuoi usare i join perché prendono per sempre. Piuttosto gestirlo nel codice e mappare nel codice è meglio.
user1735921

1
Lega che i tuoi join non funzionano abbastanza velocemente, potresti non avere un indice. Query Analyzer può essere molto utile nel confrontare le prestazioni effettive.
digital.aaron,

Sono d'accordo con Ajay Gajera, l'ho visto da solo.
user1735921

14
Che senso ha confrontare le prestazioni di due query che restituiscono risultati diversi?
Paul Spiegel,

Sì, quelle sono domande diverse ma che restituiscono lo stesso risultato
re neo

12

Le sottoquery vengono generalmente utilizzate per restituire una singola riga come valore atomico, sebbene possano essere utilizzate per confrontare valori con più righe con la parola chiave IN. Sono consentiti in quasi tutti i punti significativi di un'istruzione SQL, incluso l'elenco di destinazione, la clausola WHERE e così via. Una semplice query secondaria potrebbe essere utilizzata come condizione di ricerca. Ad esempio, tra una coppia di tabelle:

   SELECT title FROM books WHERE author_id = (SELECT id FROM authors WHERE last_name = 'Bar' AND first_name = 'Foo');

Si noti che l'utilizzo di un operatore di valore normale sui risultati di una sottoquery richiede la restituzione di un solo campo. Se sei interessato a verificare l'esistenza di un singolo valore all'interno di un insieme di altri valori, usa IN:

   SELECT title FROM books WHERE author_id IN (SELECT id FROM authors WHERE last_name ~ '^[A-E]');

Ciò è ovviamente diverso dal dire un LEFT-JOIN in cui si desidera solo unire elementi dalle tabelle A e B anche se la condizione di join non trova alcun record corrispondente nella tabella B, ecc.

Se sei solo preoccupato per la velocità, dovrai controllare con il tuo database e scrivere una buona query e vedere se c'è qualche differenza significativa nelle prestazioni.


11

Versione MySQL: 5.5.28-0ubuntu0.12.04.2-log

Ho anche avuto l'impressione che JOIN sia sempre meglio di una sottoquery in MySQL, ma EXPLAIN è un modo migliore per esprimere un giudizio. Ecco un esempio in cui le query secondarie funzionano meglio dei JOIN.

Ecco la mia query con 3 sottoquery:

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=43) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=55) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

EXPLAIN mostra:

+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
| id | select_type        | table    | type   | possible_keys                                       | key          | key_len | ref                                             | rows | Extra                    |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
|  1 | PRIMARY            | vrl      | index  | PRIMARY                                             | moved_date   | 8       | NULL                                            |  200 | Using where              |
|  1 | PRIMARY            | l        | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  1 | PRIMARY            | vrlih    | eq_ref | PRIMARY                                             | PRIMARY      | 9       | ranker.vrl.list_id,ranker.vrl.ontology_id,const |    1 | Using where              |
|  1 | PRIMARY            | lbs      | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  4 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  3 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+

La stessa query con JOIN è:

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
LEFT JOIN list_tag lt1 ON lt1.list_id = vrl.list_id AND lt1.tag_id = 43 
LEFT JOIN list_tag lt2 ON lt2.list_id = vrl.list_id AND lt2.tag_id = 55 
INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
AND lt1.list_id IS NULL AND lt2.tag_id IS NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

e l'output è:

+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys                                       | key          | key_len | ref                                         | rows | Extra                                        |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | lt3   | ref    | list_tag_key,list_id,tag_id                         | tag_id       | 5       | const                                       | 2386 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | l     | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.lt3.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | vrlih | ref    | PRIMARY                                             | PRIMARY      | 4       | ranker.lt3.list_id                          |  103 | Using where                                  |
|  1 | SIMPLE      | vrl   | ref    | PRIMARY                                             | PRIMARY      | 8       | ranker.lt3.list_id,ranker.vrlih.ontology_id |   65 | Using where                                  |
|  1 | SIMPLE      | lt1   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index; Not exists         |
|  1 | SIMPLE      | lbs   | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | lt2   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index                     |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+

Un confronto della rowscolonna indica la differenza e la query con JOIN sta utilizzando Using temporary; Using filesort.

Naturalmente quando eseguo entrambe le query, la prima viene eseguita in 0,02 secondi, la seconda non viene completata nemmeno dopo 1 minuto, quindi EXPLAIN ha spiegato correttamente queste query.

Se non ho INNER JOIN sul list_tagtavolo, ad esempio se rimuovo

AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL  

dalla prima query e corrispondentemente:

INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403

dalla seconda query, quindi EXPLAIN restituisce lo stesso numero di righe per entrambe le query ed entrambe queste query vengono eseguite altrettanto velocemente.


Ho una situazione simile, ma con più join dei tuoi, proverò a spiegare una volta
pahnin,

In Oracle o PostgreSQL avrei provato: E NON ESISTE (SELEZIONA 1 DA list_tag DOVE list_id = l.list_id E tag_id in (43, 55, 246403))
David Aldridge

11

Le sottoquery hanno la capacità di calcolare le funzioni di aggregazione al volo. Ad esempio, trova il prezzo minimo del libro e ottieni tutti i libri venduti con questo prezzo. 1) Utilizzo di sottoquery:

SELECT titles, price
FROM Books, Orders
WHERE price = 
(SELECT MIN(price)
 FROM Orders) AND (Books.ID=Orders.ID);

2) utilizzando JOINs

SELECT MIN(price)
     FROM Orders;
-----------------
2.99

SELECT titles, price
FROM Books b
INNER JOIN  Orders o
ON b.ID = o.ID
WHERE o.price = 2.99;

Un altro caso: più messaggi GROUP BYcon diverse tabelle: stackoverflow.com/questions/11415284/… Le sottoquery sembrano essere strettamente più generali. Vedi anche l'uomo di MySQL: dev.mysql.com/doc/refman/5.7/en/optimizing-subqueries.html | dev.mysql.com/doc/refman/5.7/en/rewriting-subqueries.html
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

6
-1 Ciò è fuorviante in quanto si utilizza una sottoquery e si unisce in entrambi gli esempi. Il fatto che tu abbia estratto la sottoquery in una seconda query per determinare il prezzo dell'ordine più basso non ha alcun effetto poiché il database farà esattamente la stessa cosa. Inoltre, non stai riscrivendo il join utilizzando una sottoquery; entrambe le query utilizzano un join. Si è corretto che sottoquery consentono funzioni di aggregazione, ma questo esempio non dimostra questo fatto.
David Harkness,

Sono d'accordo con David e puoi usare group by per ottenere il prezzo minimo.
user1735921

9
  • Una regola generale è che i join sono più veloci nella maggior parte dei casi (99%).
  • Più tabelle di dati hanno, le subquery sono più lente.
  • Meno tabelle di dati hanno, le sottoquery hanno una velocità equivalente come join .
  • Le sottoquery sono più semplici, più comprensibili e più facili da leggere.
  • La maggior parte dei framework di web e app e dei loro "ORM" e "Record attivi" generano query con subquery , perché con le subquery è più facile dividere la responsabilità, mantenere il codice, ecc.
  • Per i siti Web più piccoli o le app le query secondarie sono OK, ma per i siti Web e le app più grandi spesso è necessario riscrivere le query generate per unire le query, in particolare se una query utilizza molte query secondarie nella query.

Alcuni affermano che "alcuni RDBMS possono riscrivere una sottoquery in un'unione o un'unione in una sottoquery quando ritiene che una sia più veloce dell'altra.", Ma questa affermazione si applica a casi semplici, sicuramente non per query complicate con sottoquery che in realtà causano un problemi di prestazione.


> ma questa affermazione si applica a casi semplici. Capisco che sia un caso semplice che può essere riscritto in "JOIN" da RDBMS, oppure è un caso così complesso che le sottoquery sono appropriate qui. :-) Bel punto sugli ORM. Penso che questo abbia il maggiore impatto.
Pilat,

4

La differenza si vede solo quando la seconda tabella di join ha molti più dati rispetto alla tabella primaria. Ho avuto un'esperienza come sotto ...

Avevamo una tabella di utenti di centomila voci e i loro dati di appartenenza (amicizia) circa 3.000 migliaia di voci. Era un'affermazione congiunta per prendere amici e i loro dati, ma con un grande ritardo. Ma funzionava bene dove c'erano solo una piccola quantità di dati nella tabella dei membri. Una volta modificato per utilizzare una sottoquery, ha funzionato bene.

Ma nel frattempo le query di join funzionano con altre tabelle che hanno meno voci rispetto alla tabella principale.

Quindi penso che le istruzioni join e sub query stiano funzionando bene e dipende dai dati e dalla situazione.


3

In questi giorni, molti dbs possono ottimizzare subquery e join. Quindi, devi solo esaminare la tua query usando spiega e vedere quale è più veloce. Se non c'è molta differenza nelle prestazioni, preferisco usare la subquery in quanto sono semplici e più facili da capire.


1

Sto solo pensando allo stesso problema, ma sto usando la subquery nella parte FROM. Ho bisogno di connettermi e interrogare da tabelle di grandi dimensioni, la tabella "slave" ha 28 milioni di record, ma il risultato è solo 128 così piccoli risultati big data! Sto usando la funzione MAX () su di esso.

In primo luogo sto usando LEFT JOIN perché penso che sia il modo corretto, il mysql può ottimizzarlo, ecc. Seconda volta solo per i test, riscrivo per selezionare i sottotitoli rispetto a JOIN.

Runtime JOIN LEFT: runtime SUB-SELECT 1.12s: 0.06s

18 volte più veloce della selezione rispetto al join! Proprio nell'avvocato chokito. La sottoselezione sembra terribile, ma il risultato ...


-1

Se vuoi velocizzare la tua query usando join:

Per "join / join interno", non utilizzare dove condizione invece utilizzarlo in condizione "ON". Per esempio:

     select id,name from table1 a  
   join table2 b on a.name=b.name
   where id='123'

 Try,

    select id,name from table1 a  
   join table2 b on a.name=b.name and a.id='123'

Per "Join Left / Right", non utilizzare in condizione "ON", perché se si utilizza join left / right otterrà tutte le righe per una sola tabella. Quindi, non è possibile utilizzarlo in "On". Quindi, prova a utilizzare la condizione "Where"


Ciò dipende dal server SQL e dalla complessità della query. Molte implementazioni SQL ottimizzerebbero query semplici come questa per le migliori prestazioni. Forse fornire un esempio di nome e versione del server in cui questo comportamento accade per migliorare la risposta?
Trendfischer,
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.