Junção vs. subconsulta


837

Eu sou um usuário antigo do MySQL e sempre preferi JOINa subconsulta. Mas hoje em dia todo mundo usa subconsulta, e eu odeio isso; Não sei porque.

Não tenho o conhecimento teórico para julgar por mim mesmo se há alguma diferença. Uma subconsulta é tão boa quanto uma JOINe, portanto, não há nada com que se preocupar?


23
As subconsultas são ótimas às vezes. Eles sugam o desempenho no MySQL. Não os use.
Runrig

8
Sempre tive a impressão de que as subconsultas eram executadas implicitamente como junções, quando disponíveis em determinadas tecnologias de banco de dados.
Kezzer 28/05

18
As subconsultas nem sempre são uma merda. Ao ingressar em tabelas muito grandes, a maneira preferida é fazer uma sub-seleção dessa tabela grande (limitando o número de linhas) e depois ingressar.
Ovais.tariq

136
"hoje em dia todo mundo usa subconsulta" [citação necessário]
Piskvor deixou o prédio

3
Potencialmente relacionado (embora muito mais específico): stackoverflow.com/questions/141278/subqueries-vs-joins/…
Leigh Brenecki 29/03

Respostas:


191

Retirado do manual do MySQL ( 13.2.10.11 Reescrevendo subconsultas como associações ):

Uma junção [ESQUERDA] ESQUERDA pode ser mais rápida que uma subconsulta equivalente, porque o servidor pode otimizar melhor - um fato que não é específico apenas ao MySQL Server.

Portanto, as subconsultas podem ser mais lentas que LEFT [OUTER] JOIN, mas, na minha opinião, a força delas é uma legibilidade um pouco maior.


45
@ user1735921 IMO depende ... Geralmente, é muito importante a legibilidade do código, porque é de grande importância para o gerenciamento posterior ... Vamos lembrar a famosa declaração de Donald Knuth: "A otimização prematura é a raiz de tudo mal (ou pelo menos a maior parte) na programação " . No entanto, naturalmente, existem áreas onde o desempenho é fundamental ... Idealmente programação, quando se consegue uma reconciliação com outro :)
simhumileco

31
Em consultas mais complexas, acho as junções muito mais fáceis de ler do que as subconsultas. subconsultas se transformam em uma tigela de macarrão na minha cabeça.
Zahra

6
@ user1735921 claro, especialmente quando a consulta fica tão complicada que faz a coisa errada e você passa um dia consertando-a ... há um equilíbrio entre elas, como sempre.
Fabio.sussetto

6
@ user1735921 Só se os ganhos de desempenho são vale o aumento no tempo de manutenção necessária no futuro
Joshua Schlichting

3
Minha opinião Joine sub querytem sintaxe diferente, portanto, a legibilidade não podemos comparar, ambas têm maior legibilidade, desde que você seja bom na sintaxe SQL. O desempenho é mais importante.
Thavaprakash Swaminathan

842

As subconsultas são a maneira logicamente correta de resolver problemas do formulário "Obter fatos de A, condicional aos fatos de B". Nesses casos, faz mais sentido lógico colocar B em uma subconsulta do que fazer uma associação. Também é mais seguro, no sentido prático, já que você não precisa ser cauteloso ao obter fatos duplicados de A devido a várias partidas contra B.

Praticamente falando, no entanto, a resposta geralmente se resume ao desempenho. Alguns otimizadores sugam limões quando recebem uma junção versus uma subconsulta, e outros sugam limões de outra maneira, e isso é específico do otimizador, específico da versão do DBMS e específico da consulta.

Historicamente, as junções explícitas geralmente vencem, daí a sabedoria estabelecida de que as junções são melhores, mas os otimizadores estão melhorando o tempo todo e, portanto, prefiro escrever as consultas primeiro de uma maneira logicamente coerente e depois reestruturar se as restrições de desempenho justificarem isso.


105
Ótima resposta. Eu também acrescentaria que os desenvolvedores (especialmente os amadores) nem sempre são proficientes em SQL.
Álvaro González

4
+1 Procurando alguma explicação lógica para este problema por um longo tempo, esta é única resposta que parece lógico para mim
Ali Umair

1
@ Marcelo Cantos, você poderia dar um exemplo de sua afirmação "Também é mais seguro, no sentido prático, já que você não precisa ser cauteloso em obter fatos duplicados de A devido a várias partidas contra B."? Achei isso muito perspicaz, mas um pouco abstrato demais. Obrigado.
Jinghui Niu 23/09/16

6
@JinghuiNiu Clientes que compraram itens caros: select custid from cust join bought using (custid) where price > 500. Se um cliente comprou vários itens caros, você receberá dobradas. Para consertar isso select custid from cust where exists (select * from bought where custid = cust.custid and price > 500),. Você pode usar select distinct …, mas geralmente é mais trabalhoso, tanto para o otimizador quanto para o avaliador.
Marcelo Cantos

1
@MatTheWhale sim, eu usei uma resposta simplificada porque eu era preguiçoso. Em um cenário real, você estaria puxando mais colunas do que apenas custid de cust.
Marcelo Cantos

357

Na maioria dos casos, JOINs são mais rápidos que as subconsultas e é muito raro uma subconsulta ser mais rápida.

No JOINs, o RDBMS pode criar um plano de execução melhor para sua consulta e prever quais dados devem ser carregados para serem processados ​​e economizar tempo, ao contrário da subconsulta em que ele executará todas as consultas e carregará todos os dados para fazer o processamento. .

O bom das subconsultas é que elas são mais legíveis que JOINs: é por isso que a maioria das pessoas novas do SQL prefere; é o caminho mais fácil; mas quando se trata de desempenho, o JOINS é melhor na maioria dos casos, mesmo que também não seja difícil de ler.


14
Sim, a maioria dos bancos de dados, portanto, o inclui como uma etapa de otimização para converter subconsultas em junções ao analisar sua consulta.
Cine

16
Esta resposta é um pouco simplificada para a pergunta que foi feita. Como você afirma: certas subconsultas estão ok e outras não. A resposta realmente não ajuda a distinguir os dois. (também o 'muito raro' realmente depende dos seus dados / aplicativo).
Desrazão 28/05

21
você pode provar algum de seus pontos com referência à documentação ou resultados de testes?
Uğur Gümüşhan

62
Fiz experiências muito boas com subconsultas que contêm uma referência retroativa à consulta superior, especialmente quando se trata de contagens de linhas acima de 100.000. A coisa parece ser uso de memória e paginação para o arquivo de troca. Uma junção produziria uma quantidade muito grande de dados, que podem não caber na memória e devem ser paginados no arquivo de troca. Sempre que esse for o caso, o tempo de consulta de pequenas sub-seleções como select * from a where a.x = (select b.x form b where b.id = a.id)é extremamente pequeno comparado a uma junção. Esse é um problema muito específico, mas, em alguns casos, leva você de horas a minutos.
Zuloo 28/11

13
Tenho experiência com Oracle e posso dizer que as subconsultas são muito melhores em tabelas grandes se você não tiver nenhuma filtragem ou classificação nelas.
Amir Pashazadeh

130

Use EXPLAIN para ver como seu banco de dados executa a consulta em seus dados. Existe uma enorme "depende" nesta resposta ...

O PostgreSQL pode reescrever uma subconsulta em uma junção ou uma junção em uma subconsulta quando achar que uma é mais rápida que a outra. Tudo depende dos dados, índices, correlação, quantidade de dados, consulta, etc.


6
é exatamente por isso que o postgresql é tão bom e útil que ele entende qual é o objetivo e corrigirá uma consulta com base no que acha melhor e o postgresql é muito bom em saber como analisar seus dados
WojonsTech

heww. Eu acho que não há necessidade de reescrever toneladas de consultas para mim! postgresql para a vitória.
Daniel Shin

77

No ano de 2010, eu teria me juntado ao autor dessas perguntas e teria votado fortemente JOIN, mas com muito mais experiência (especialmente no MySQL), posso afirmar: Sim, as subconsultas podem ser melhores. Eu li várias respostas aqui; algumas subconsultas declaradas são mais rápidas, mas faltavam uma boa explicação. Espero poder fornecer uma com esta (muito) resposta tardia:

Antes de mais, deixe-me dizer o mais importante: existem diferentes formas de subconsultas

E a segunda declaração importante: o tamanho importa

Se você usar subconsultas, esteja ciente de como o DB-Server executa a subconsulta. Especialmente se a subconsulta for avaliada uma vez ou para cada linha! Por outro lado, um servidor de banco de dados moderno é capaz de otimizar muito. Em alguns casos, uma subconsulta ajuda a otimizar uma consulta, mas uma versão mais recente do DB-Server pode tornar a otimização obsoleta.

Subconsultas em campos selecionados

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

Esteja ciente de que uma subconsulta é executada para cada linha resultante foo.
Evite isso, se possível; isso pode diminuir drasticamente a sua consulta em grandes conjuntos de dados. No entanto, se a subconsulta não tiver nenhuma referência, fooela poderá ser otimizada pelo servidor de banco de dados como conteúdo estático e poderá ser avaliada apenas uma vez.

Subconsultas na instrução Where

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

Se você tiver sorte, o banco de dados otimiza isso internamente em um JOIN. Caso contrário, sua consulta se tornará muito, muito lenta em grandes conjuntos de dados, porque executará a subconsulta para cada linha foo, não apenas os resultados, como no tipo de seleção.

Subconsultas na instrução Join

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

Isto é interessante. Combinamos JOINcom uma subconsulta. E aqui temos a força real das subconsultas. Imagine um conjunto de dados com milhões de linhas, wilcomas apenas algumas distintas me. Em vez de ingressar em uma mesa enorme, agora temos uma mesa temporária menor para participar. Isso pode resultar em consultas muito mais rápidas, dependendo do tamanho do banco de dados. Você pode ter o mesmo efeito com CREATE TEMPORARY TABLE ...e INSERT INTO ... SELECT ..., o que pode fornecer melhor legibilidade em consultas muito complexas (mas pode bloquear conjuntos de dados em um nível de isolamento de leitura repetível).

Subconsultas aninhadas

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

Você pode aninhar subconsultas em vários níveis. Isso pode ajudar em grandes conjuntos de dados se você precisar agrupar ou classificar os resultados. Normalmente, o servidor de banco de dados cria uma tabela temporária para isso, mas às vezes você não precisa classificar a tabela inteira, apenas o conjunto de resultados. Isso pode fornecer um desempenho muito melhor, dependendo do tamanho da tabela.

Conclusão

As subconsultas não substituem a JOINe você não deve usá-las dessa maneira (embora possível). Na minha humilde opinião, o uso correto de uma subconsulta é o uso como uma substituição rápida de CREATE TEMPORARY TABLE .... Uma boa subconsulta reduz um conjunto de dados de uma maneira que você não pode realizar na ONinstrução a JOIN. Se uma sub-consulta tem uma das palavras-chave GROUP BYou DISTINCT, de preferência não está situado nos campos selecionados ou a declaração em que, em seguida, pode melhorar o desempenho muito.


3
Para Sub-queries in the Join-statement: (1) gerar tabela derivada a partir da própria subconsulta pode demorar muito tempo. (2) a tabela derivada resultante não é indexada. esses dois por si só podem desacelerar significativamente o SQL.
JXC

@jxc Só posso falar pelo MySQL (1) Existe uma tabela temporária semelhante a uma junção. O tempo depende da quantidade de dados. Se você não pode reduzir os dados com uma subconsulta, use uma associação. (2) Isso mesmo, depende do fator em que você pode reduzir os dados na tabela temporária. Eu tinha casos do mundo real, onde eu podia reduzir o tamanho da junção de alguns milhões para algumas centenas e reduzir o tempo de consulta de vários segundos (com uso total do índice) para um quarto de segundo com uma subconsulta.
Trendfischer 12/03/19

IMO: (1) essa tabela temporária (tabela derivada) não é materializada; portanto, toda vez que você executa o SQL, a tabela temporária deve ser recriada, o que pode ser muito caro e um gargalo real (ou seja, executar um grupo com milhões de registros) (2), mesmo que você possa reduzir o tamanho da tabela temporária para 10registros, já que não há índice, isso ainda significa potencialmente consultar 9 vezes mais registros de dados do que a tabela temporária ao ingressar em outras tabelas. BTW eu tive esse problema antes com o meu db (MySQL), no meu caso, usando subconsulta em SELECT listpoderia ser muito mais rápido.
JXC

@jxc Não duvido que existam muitos exemplos, em que usar uma subconsulta é menos ideal. Como boa prática, você deve usar EXPLAINem uma consulta antes de otimizar. Com o antigo, set profiling=1você pode ver facilmente, se uma tabela temporária é um gargalo. E mesmo que um índice precise de tempo de processamento, as B-Trees otimizam a consulta de registros, mas uma tabela de 10 registros pode ser muito mais rápida que um índice para milhões de registros. Mas isso depende de vários fatores, como tamanhos e tipos de campos.
Trendfischer 13/03/19

1
Gostei muito da sua explicação. Obrigado.
unpairestgood

43

Antes de tudo, para comparar os dois primeiro, você deve distinguir consultas com subconsultas para:

  1. uma classe de subconsultas que sempre têm consulta equivalente correspondente gravada com junções
  2. uma classe de subconsultas que não podem ser reescritas usando junções

Para a primeira classe de consultas, um bom RDBMS verá junções e subconsultas como equivalentes e produzirá os mesmos planos de consulta.

Hoje em dia, até o mysql faz isso.

Ainda assim, às vezes isso não acontece, mas isso não significa que as junções sempre vencerão - eu tive casos ao usar subconsultas no mysql para melhorar o desempenho. (Por exemplo, se houver algo impedindo o planejador do mysql estimar corretamente o custo e se o planejador não vir a variante de junção e a variante de subconsulta da mesma forma, as subconsultas poderão superar as junções forçando um determinado caminho).

A conclusão é que você deve testar suas consultas para as variantes de junção e subconsulta, se quiser ter certeza de qual delas terá um desempenho melhor.

Para a segunda classe, a comparação não faz sentido, pois essas consultas não podem ser reescritas usando junções e, nesses casos, as subconsultas são uma maneira natural de executar as tarefas necessárias e você não deve discriminá-las.


1
você pode fornecer um exemplo de consulta escrita usando subconsultas que não podem ser convertidas em junções (segunda classe, como você chama)?
Zahra

24

Eu acho que o que foi subestimado nas respostas citadas é a questão de duplicatas e resultados problemáticos que podem surgir de casos específicos (de uso).

(embora Marcelo Cantos o mencione)

Citarei o exemplo dos cursos Lagunita de Stanford sobre SQL.

Tabela do aluno

+------+--------+------+--------+
| 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 |
+------+--------+------+--------+

Aplicar tabela

(pedidos feitos para universidades e cursos específicos)

+------+----------+----------------+----------+
| 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        |
+------+----------+----------------+----------+

Vamos tentar encontrar as pontuações do GPA para estudantes que se inscreveram no ensino médio CS(independentemente da universidade)

Usando uma subconsulta:

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 |
+------+

O valor médio para este conjunto de resultados é:

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

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

Usando uma junção:

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 |
+------+

valor médio para este conjunto de resultados:

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

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

É óbvio que a segunda tentativa produz resultados enganosos em nosso caso de uso, uma vez que conta duplicatas para o cálculo do valor médio. Também é evidente que o uso da distinctdeclaração baseada em junção não eliminará o problema, pois manterá erroneamente uma das três ocorrências da 3.9pontuação. O caso correto é contabilizar DUAS (2) ocorrências da 3.9pontuação, uma vez que realmente temos DOIS (2) alunos com essa pontuação que atendem aos nossos critérios de consulta.

Parece que, em alguns casos, uma subconsulta é o caminho mais seguro, além de quaisquer problemas de desempenho.


Eu acho que você não pode usar uma subconsulta aqui. Este não é um caso em que você pode usar logicamente, mas um fornece uma resposta errada por causa da implementação técnica do mesmo. Este é um caso em que você NÃO PODE usar uma subconsulta porque um aluno que não pertence ao CS pode pontuar 3,9, que está na lista de pontuações IN. O contexto do CS é perdido quando a subconsulta é executada, o que não é o que queremos logicamente. Portanto, este não é um bom exemplo em que qualquer um pode ser usado. O uso da subconsulta é conceitual / logicamente errado para esse caso de uso, mesmo que felizmente ele dê o resultado certo para um conjunto de dados diferente.
Saurabh Patil

22

A documentação do MSDN para SQL Server diz

Muitas instruções Transact-SQL que incluem subconsultas podem ser formuladas alternativamente como junções. Outras perguntas podem ser feitas apenas com subconsultas. No Transact-SQL, geralmente não há diferença de desempenho entre uma instrução que inclui uma subconsulta e uma versão semanticamente equivalente que não. No entanto, em alguns casos em que a existência deve ser verificada, uma junção produz um melhor desempenho. Caso contrário, a consulta aninhada deve ser processada para cada resultado da consulta externa para garantir a eliminação de duplicatas. Nesses casos, uma abordagem de junção produziria melhores resultados.

então se você precisar de algo como

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

tente usar join. Em outros casos, não faz diferença.

Eu digo: A criação de funções para subconsultas elimina o problema do cluttter e permite implementar lógica adicional nas subconsultas. Portanto, recomendo criar funções para subconsultas sempre que possível.

A desordem no código é um grande problema e a indústria trabalha para evitá-lo há décadas.


9
Substituir subconsultas por funções é uma péssima idéia em termos de desempenho em alguns RDBMS (por exemplo, Oracle); portanto, recomendo o contrário - use subconsultas / associações em vez de funções, sempre que possível.
Frank Schmitt

3
@FrankSchmitt, por favor, apoie seu argumento com referências.
Uğur Gümüşhan

2
Também existem casos em que você deve usar uma subconsulta em vez de uma junção, mesmo se verificar a existência: se verificar NOT EXISTS. A NOT EXISTSvence uma LEFT OUTER JOIN por várias razões: desempenho, segurança contra falhas (no caso de colunas nuláveis) e legibilidade. sqlperformance.com/2012/12/t-sql-queries/left-anti-semi-join
Tim Schmelter 14/13

16

Execute em um banco de dados muito grande a partir de um antigo Mambo CMS:

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

0 segundos

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

~ 3 segundos

Um EXPLAIN mostra que eles examinam exatamente o mesmo número de linhas, mas um leva 3 segundos e o outro é quase instantâneo. Moral da história? Se o desempenho é importante (quando não é?), Tente de várias maneiras e veja qual é o mais rápido.

E...

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

0 segundos

Novamente, mesmos resultados, mesmo número de linhas examinadas. Meu palpite é que DISTINCT mos_content.catid leva muito mais tempo para descobrir do que DISTINCT mos_categories.id.


1
eu gostaria de saber mais sobre o que você está tentando apontar na última linha "Meu palpite é que DISTINCT mos_content.catid leva muito mais tempo para descobrir do que DISTINCT mos_categories.id." . Você está dizendo que um ID deve ter apenas um nome ide não algo como esse catid? Tentando otimizar meus acessos db, e seus aprendizados podem ajudar.
Bool.dev 21/10

2
usar o SQL IN nesse caso é uma prática ruim e não prova nada.
Uğur Gümüşhan

15

Conforme minha observação, como dois casos, se uma tabela tiver menos de 100.000 registros, a junção funcionará rapidamente.

Porém, no caso de uma tabela ter mais de 100.000 registros, uma subconsulta é o melhor resultado.

Eu tenho uma tabela que possui 500.000 registros que criei abaixo da consulta e seu tempo de resultado é como

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

Resultado: 13.3 Segundos

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

Resultado: 1.65 Segundos


Concordo que, às vezes, quebrar a consulta também funciona; quando você tem milhões de registros, não deseja usar junções porque elas levam para sempre. Em vez disso, lide com o código e o mapa no código é melhor.
user1735921

1
Amarre suas junções não estão funcionando rápido o suficiente, pode estar faltando um índice. O Query Analyzer pode ser bastante útil na comparação do desempenho real.
digital.aaron

Eu concordo com Ajay Gajera, eu já vi isso por mim.
user1735921

14
Como faz sentido comparar o desempenho de duas consultas que retornam resultados diferentes?
Paul Spiegel

Sim, essas são consultas diferentes, mas retornam o mesmo resultado
King neo

12

As subconsultas geralmente são usadas para retornar uma única linha como um valor atômico, embora possam ser usadas para comparar valores com várias linhas com a palavra-chave IN. Eles são permitidos em praticamente qualquer ponto significativo em uma instrução SQL, incluindo a lista de destino, a cláusula WHERE e assim por diante. Uma subconsulta simples pode ser usada como condição de pesquisa. Por exemplo, entre um par de tabelas:

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

Observe que o uso de um operador de valor normal nos resultados de uma subconsulta requer que apenas um campo seja retornado. Se você estiver interessado em verificar a existência de um único valor em um conjunto de outros valores, use IN:

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

Isso é obviamente diferente de, digamos, LEFT-JOIN, no qual você apenas deseja juntar itens das tabelas A e B, mesmo que a condição de junção não encontre nenhum registro correspondente na tabela B, etc.

Se você está preocupado apenas com a velocidade, precisará verificar com o banco de dados e escrever uma boa consulta e verificar se há alguma diferença significativa no desempenho.


11

Versão do MySQL: 5.5.28-0ubuntu0.12.04.2-log

Também tive a impressão de que JOIN é sempre melhor que uma subconsulta no MySQL, mas EXPLAIN é a melhor maneira de fazer um julgamento. Aqui está um exemplo em que as subconsultas funcionam melhor que JOINs.

Aqui está minha consulta com 3 subconsultas:

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 |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+

A mesma consulta com JOINs é:

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 a saída é:

+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
| 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                     |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+

Uma comparação da rowscoluna indica a diferença e a consulta com JOINs está usando Using temporary; Using filesort.

Obviamente, quando executo as duas consultas, a primeira é feita em 0,02 segundos, a segunda não é concluída mesmo após 1 minuto, por isso EXPLAIN explicou essas consultas corretamente.

Se eu não tiver o INNER JOIN na list_tagmesa, ou seja, se eu remover

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

da primeira consulta e correspondentemente:

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

a partir da segunda consulta, EXPLAIN retorna o mesmo número de linhas para ambas as consultas e essas consultas são executadas igualmente rápido.


Tenho situação semelhante, mas com mais junta-se que o seu, vai tentar com explicar uma vez
pahnin

No Oracle ou no PostgreSQL, eu teria tentado: AND NOT EXISTS (SELECT 1 FROM list_tag WHERE list_id = l.list_id AND tag_id in (43, 55, 246403))
David Aldridge

11

As subconsultas têm a capacidade de calcular funções de agregação rapidamente. Por exemplo, encontre o preço mínimo do livro e obtenha todos os livros vendidos com esse preço. 1) Usando subconsultas:

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

2) usando 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;

Outro caso: vários GROUP BYs com tabelas diferentes: stackoverflow.com/questions/11415284/… As subconsultas parecem ser estritamente mais gerais. Veja também o homem do 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 escreveu:

6
-1 Isso é enganoso, pois você está usando uma subconsulta e se une nos dois exemplos. O fato de você ter extraído a subconsulta para uma segunda consulta para determinar o preço do pedido mais baixo não tem efeito, pois o banco de dados fará exatamente a mesma coisa. Além disso, você não está reescrevendo a junção usando uma subconsulta; ambas as consultas usam uma junção. Você está certo de que as subconsultas permitem funções agregadas, mas este exemplo não demonstra esse fato.
David Harkness

Eu concordo com David, e você pode usar o grupo por para obter o preço mínimo.
user1735921

9
  • Uma regra geral é que as junções são mais rápidas na maioria dos casos (99%).
  • Quanto mais tabelas de dados houver, as subconsultas serão mais lentas.
  • Quanto menos tabelas de dados tiverem, as subconsultas terão velocidade equivalente às junções .
  • As subconsultas são mais simples, mais fáceis de entender e mais fáceis de ler.
  • A maioria das estruturas da Web e de aplicativos e seus "ORM" e "Registro ativo" geram consultas com subconsultas , porque com subconsultas é mais fácil dividir responsabilidades, manter código etc.
  • Para sites ou aplicativos menores da web, as subconsultas são boas, mas para sites e aplicativos maiores, você precisará reescrever consultas geradas para ingressar em consultas, especialmente se uma consulta usar muitas subconsultas na consulta.

Algumas pessoas dizem que "alguns RDBMS podem reescrever uma subconsulta em uma associação ou uma associação a uma subconsulta quando achar que uma é mais rápida que a outra.", Mas essa declaração se aplica a casos simples, certamente não para consultas complicadas com subconsultas que realmente causam uma problemas no desempenho.


> mas esta declaração se aplica a casos simples. Entendo que é um caso simples que pode ser reescrito para "JOIN" pelo RDBMS ou é um caso tão complexo que as subconsultas são apropriadas aqui. :-) Bom ponto sobre ORMs. Eu acho que isso tem o maior impacto.
pilat 6/03/19

4

A diferença é vista apenas quando a segunda tabela de junção possui significativamente mais dados que a tabela principal. Eu tive uma experiência como abaixo ...

Tivemos uma tabela de usuários com cem mil entradas e seus dados de associação (amizade) com cerca de 300 mil entradas. Foi uma declaração de junção para levar os amigos e seus dados, mas com um grande atraso. Mas estava funcionando bem, onde havia apenas uma pequena quantidade de dados na tabela de associação. Depois que o alteramos para usar uma subconsulta, funcionou bem.

Entretanto, as consultas de junção estão funcionando com outras tabelas que possuem menos entradas que a tabela principal.

Então, acho que as instruções de junção e subconsulta estão funcionando bem e isso depende dos dados e da situação.


3

Hoje em dia, muitos dbs podem otimizar subconsultas e associações. Portanto, basta examinar sua consulta usando o Explique e veja qual é mais rápida. Se não houver muita diferença no desempenho, prefiro usar a subconsulta, pois elas são simples e fáceis de entender.


1

Estou apenas pensando no mesmo problema, mas estou usando subconsulta na parte FROM. Eu preciso conectar e consultar de tabelas grandes, a tabela "escrava" tem 28 milhões de registros, mas o resultado é apenas 128, portanto, resultado tão pequeno quanto o big data! Estou usando a função MAX () nele.

Primeiro estou usando LEFT JOIN porque acho que é o caminho correto, o mysql pode otimizar etc. Segunda vez apenas para teste, reescrevo para sub-selecionar contra o JOIN.

Tempo de execução LEFT JOIN: 1.12s Tempo de execução SUB-SELECT: 0.06s

A subseleção 18 vezes mais rápida que a junção! Apenas no adv chokito. A subseleção parece terrível, mas o resultado ...


-1

Se você deseja acelerar sua consulta usando join:

Para "junção interna / junção", não use a condição where, em vez disso, use-a na condição "ON". Por exemplo:

     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'

Para "Junção esquerda / direita", não use na condição "ATIVADO", pois se você usar a junção esquerda / direita, todas as linhas serão exibidas para qualquer tabela. Então, tente usar a condição "Onde"


Isso depende do servidor SQL e da complexidade da consulta. Muitas implementações de SQL otimizariam consultas simples como essa para obter o melhor desempenho. Talvez forneça um exemplo de nome e versão do servidor em que esse comportamento ocorra para melhorar a resposta?
Trendfischer
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.