COMPARANDO SEMI JOINS

Existem alguns mitos envolvendo o que conceitualmente é conhecido como semi join, e, infelizmente, tais mitos acabam por prejudicar a escrita de consultas, causando problemas de performance. Mas, o que são semi joins? São joins que retornam linhas de uma tabela A baseado na existência de linhas correlacionadas em uma tabela B. Se a consulta retorna apenas atributos (campos) da tabela da esquerda, então o join é chamado de Left Semi Join; se retorna apenas atributos da tabela da direita, então é chamado de Right Semi Join. Um semi join pode ser produzido usando INNER JOINS, EXISTS, IN e também com o INTERSECT.

Antes de entrarmos em mais detalhes, listemos os mitos comuns envolvendo semi joins:

  • JOIN versus EXISTS
  • IN versus EXISTS

Será que realmente existe ganho de performance ao adotar, por exemplo, o EXISTS ao invés do IN? Iremos clarificar essa e outras questões no decorrer do artigo.

PREPARANDO O AMBIENTE

Antes de entrarmos diretamente no assunto, vamos executar o código abaixo para a criação do nosso ambiente de estudo. Estou utilizando o banco AdventureWorks2012 como referência.

[et_pb_dmb_code_snippet _builder_version=”4.0.6″ code=”LS0gQ3JpYSBvIGFtYmllbnRlCnVzZSB0ZW1wZGIKZ28KQ1JFQVRFIFRBQkxFIGRiby5DbGllbnRlcwooICAgICAgQ2xpZW50ZUlEIElOVCBOT1QgTlVMTCBQUklNQVJZIEtFWSwKTm9tZSBWQVJDSEFSKDUwKSBOT1QgTlVMTCwKU29icmVub21lIFZBUkNIQVIoNTApIE5PVCBOVUxMLAopOwpJTlNFUlQgZGJvLkNsaWVudGVzClNFTEVDVApjLkN1c3RvbWVySUQKLCBwLkZpcnN0TmFtZQosIHAuTGFzdE5hbWUKRlJPTSBBZHZlbnR1cmVXb3JrczIwMTIuU2FsZXMuQ3VzdG9tZXIgYwpKT0lOIEFkdmVudHVyZVdvcmtzMjAxMi5QZXJzb24uUGVyc29uIHAKT04gYy5QZXJzb25JRCA9IHAuQnVzaW5lc3NFbnRpdHlJRApHTwpDUkVBVEUgVEFCTEUgZGJvLlZlbmRhcwooICAgICAgVmVuZGFzSUQgSU5UIE5PVCBOVUxMIFBSSU1BUlkgS0VZLApEYXRhIERBVEVUSU1FIE5PVCBOVUxMLApDbGllbnRlSUQgSU5UIE5PVCBOVUxMIEZPUkVJR04gS0VZIFJFRkVSRU5DRVMgZGJvLkNsaWVudGVzIChDbGllbnRlSUQpLApUb3RhbERhVmVuZGEgREVDSU1BTCgxNSw0KSBOT1QgTlVMTAopOwpJTlNFUlQgZGJvLlZlbmRhcwpTRUxFQ1QgVE9QIDQwIFBFUkNFTlQKaC5TYWxlc09yZGVySUQKLCBoLk9yZGVyRGF0ZQosIGguQ3VzdG9tZXJJRAosIGguVG90YWxEdWUKRlJPTSBBZHZlbnR1cmVXb3JrczIwMTIuU2FsZXMuU2FsZXNPcmRlckhlYWRlciBoOwpDUkVBVEUgTk9OQ0xVU1RFUkVEIElOREVYIEFLX1ZlbmRhc19DbGllbnRlSUQKb24gZGJvLlZlbmRhcyAoQ2xpZW50ZUlEKQpHTw==” hover_enabled=”0″]LS0gQ3JpYSBvIGFtYmllbnRlCnVzZSB0ZW1wZGIKZ28KQ1JFQVRFIFRBQkxFIGRiby5DbGllbnRlcwooICAgICAgQ2xpZW50ZUlEIElOVCBOT1QgTlVMTCBQUklNQVJZIEtFWSwKTm9tZSBWQVJDSEFSKDUwKSBOT1QgTlVMTCwKU29icmVub21lIFZBUkNIQVIoNTApIE5PVCBOVUxMLAopOwpJTlNFUlQgZGJvLkNsaWVudGVzClNFTEVDVApjLkN1c3RvbWVySUQKLCBwLkZpcnN0TmFtZQosIHAuTGFzdE5hbWUKRlJPTSBBZHZlbnR1cmVXb3JrczIwMTIuU2FsZXMuQ3VzdG9tZXIgYwpKT0lOIEFkdmVudHVyZVdvcmtzMjAxMi5QZXJzb24uUGVyc29uIHAKT04gYy5QZXJzb25JRCA9IHAuQnVzaW5lc3NFbnRpdHlJRApHTwpDUkVBVEUgVEFCTEUgZGJvLlZlbmRhcwooICAgICAgVmVuZGFzSUQgSU5UIE5PVCBOVUxMIFBSSU1BUlkgS0VZLApEYXRhIERBVEVUSU1FIE5PVCBOVUxMLApDbGllbnRlSUQgSU5UIE5PVCBOVUxMIEZPUkVJR04gS0VZIFJFRkVSRU5DRVMgZGJvLkNsaWVudGVzIChDbGllbnRlSUQpLApUb3RhbERhVmVuZGEgREVDSU1BTCgxNSw0KSBOT1QgTlVMTAopOwpJTlNFUlQgZGJvLlZlbmRhcwpTRUxFQ1QgVE9QIDQwIFBFUkNFTlQKaC5TYWxlc09yZGVySUQKLCBoLk9yZGVyRGF0ZQosIGguQ3VzdG9tZXJJRAosIGguVG90YWxEdWUKRlJPTSBBZHZlbnR1cmVXb3JrczIwMTIuU2FsZXMuU2FsZXNPcmRlckhlYWRlciBoOwpDUkVBVEUgTk9OQ0xVU1RFUkVEIElOREVYIEFLX1ZlbmRhc19DbGllbnRlSUQKb24gZGJvLlZlbmRhcyAoQ2xpZW50ZUlEKQpHTw==[/et_pb_dmb_code_snippet]

INNER JOIN VERSUS IN VERSUS EXISTS

Imagine a seguinte sentença: recuperar todos os clientes que já realizaram compras na empresa?

[et_pb_dmb_code_snippet _builder_version=”4.0.6″ code=”LS0gMSkgVXNhbmRvIElOTkVSIEpPSU4gY29tIERJU1RJTkNUClNFTEVDVCBESVNUSU5DVCBjLkNsaWVudGVJRCwgYy5Ob21lLCBjLlNvYnJlbm9tZQpGUk9NIGRiby5DbGllbnRlcyBjCkpPSU4gZGJvLlZlbmRhcyB2IE9OIHYuQ2xpZW50ZUlEID0gYy5DbGllbnRlSUQ=” hover_enabled=”0″]LS0gMSkgVXNhbmRvIElOTkVSIEpPSU4gY29tIERJU1RJTkNUClNFTEVDVCBESVNUSU5DVCBjLkNsaWVudGVJRCwgYy5Ob21lLCBjLlNvYnJlbm9tZQpGUk9NIGRiby5DbGllbnRlcyBjCkpPSU4gZGJvLlZlbmRhcyB2IE9OIHYuQ2xpZW50ZUlEID0gYy5DbGllbnRlSUQ=[/et_pb_dmb_code_snippet]
[et_pb_dmb_code_snippet _builder_version=”4.0.6″ code=”4oCUIDIpIFVzYW5kbyBJTgoKU0VMRUNUIGMuQ2xpZW50ZUlELCBjLk5vbWUsIGMuU29icmVub21lCkZST00gZGJvLkNsaWVudGVzIGMKV0hFUkUgYy5DbGllbnRlSUQgSU4KKCAgICAgIFNFTEVDVCB2LkNsaWVudGVJRCBGUk9NIGRiby5WZW5kYXMgdiAgICk=” hover_enabled=”0″]4oCUIDIpIFVzYW5kbyBJTgoKU0VMRUNUIGMuQ2xpZW50ZUlELCBjLk5vbWUsIGMuU29icmVub21lCkZST00gZGJvLkNsaWVudGVzIGMKV0hFUkUgYy5DbGllbnRlSUQgSU4KKCAgICAgIFNFTEVDVCB2LkNsaWVudGVJRCBGUk9NIGRiby5WZW5kYXMgdiAgICk=[/et_pb_dmb_code_snippet]
[et_pb_dmb_code_snippet _builder_version=”4.0.6″ code=”4oCUIDMpIFVzYW5kbyBFWElTVFMKClNFTEVDVCBjLkNsaWVudGVJRCwgYy5Ob21lLCBjLlNvYnJlbm9tZQpGUk9NIGRiby5DbGllbnRlcyBjCldIRVJFIEVYSVNUUwooICAgICAgU0VMRUNUIDEgRlJPTSBkYm8uVmVuZGFzIHYKV0hFUkUgdi5DbGllbnRlSUQgPSBjLkNsaWVudGVJRCAgKQ==” hover_enabled=”0″]4oCUIDMpIFVzYW5kbyBFWElTVFMKClNFTEVDVCBjLkNsaWVudGVJRCwgYy5Ob21lLCBjLlNvYnJlbm9tZQpGUk9NIGRiby5DbGllbnRlcyBjCldIRVJFIEVYSVNUUwooICAgICAgU0VMRUNUIDEgRlJPTSBkYm8uVmVuZGFzIHYKV0hFUkUgdi5DbGllbnRlSUQgPSBjLkNsaWVudGVJRCAgKQ==[/et_pb_dmb_code_snippet]

Veja que:

  1. Os resultados gerados são os mesmos;
  2. Os planos de execução são idênticos.

Nos exemplos expostos anteriormente, para cada linha da tabela A o otimizador necessita checar se existe pelo menos uma linha correspondente na tabela B (lado oposto). Isso significa que o otimizador não precisa percorrer todas as linhas da outra tabela. Existe por aí a ideia equivocada de que quando usamos o IN o otimizador obrigatoriamente percorre todas as linhas da tabela B (o que não é verdade). O IN segue basicamente o mesmo princípio do EXISTS: fazer teste de existência. Atente que para alcançarmos o LEFT SEMI JOIN a partir do INNER JOIN, foi necessário restringir a lista de colunas da cláusula SELECT para recuperar apenas as colunas da tabela A e usarmos o DISTINCT para suprimir as linhas redundantes (este é o segredo). Apesar de não ter sido demonstrado anteriormente, também é possível alcançar um semi-join através do operador INTERSECT.

 

ANTI-SEMI JOIN (OUTER JOIN VERSUS NOT IN VERSUS NOT EXISTS)

O inverso de um semi join é um anti-semi join, que ocorre quando linhas em uma tabela A não possuem correspondentes em uma tabela B (é baseado na não existência). É possível alcançar um anti-semi join através de subconsultas utilizando NOT IN ou EXISTS, ou ainda através do operador EXCEPT. Também é possível alcançar um anti-semi join utilizando outer join, filtrando somente as outer rows.

Imagine a seguinte sentença: recuperar todos os clientes que nunca realizaram compras na empresa?

[et_pb_dmb_code_snippet _builder_version=”4.0.6″ code=”LS0gMSkgVXNhbmRvIE5PVCBJTgpTRUxFQ1QgYy5DbGllbnRlSUQsIGMuTm9tZSwgYy5Tb2JyZW5vbWUKRlJPTSBkYm8uQ2xpZW50ZXMgYwpXSEVSRSBjLkNsaWVudGVJRCBOT1QgSU4KKCAgICAgIFNFTEVDVCB2LkNsaWVudGVJRCBGUk9NIGRiby5WZW5kYXMgdiAgICk=” hover_enabled=”0″]LS0gMSkgVXNhbmRvIE5PVCBJTgpTRUxFQ1QgYy5DbGllbnRlSUQsIGMuTm9tZSwgYy5Tb2JyZW5vbWUKRlJPTSBkYm8uQ2xpZW50ZXMgYwpXSEVSRSBjLkNsaWVudGVJRCBOT1QgSU4KKCAgICAgIFNFTEVDVCB2LkNsaWVudGVJRCBGUk9NIGRiby5WZW5kYXMgdiAgICk=[/et_pb_dmb_code_snippet]
[et_pb_dmb_code_snippet _builder_version=”4.0.6″ code=”LS0gMikgVXNhbmRvIE5PVCBFWElTVFMKU0VMRUNUIGMuQ2xpZW50ZUlELCBjLk5vbWUsIGMuU29icmVub21lCkZST00gZGJvLkNsaWVudGVzIGMKV0hFUkUgTk9UIEVYSVNUUwooICAgICAgU0VMRUNUIDEgRlJPTSBkYm8uVmVuZGFzIHYKV0hFUkUgdi5DbGllbnRlSUQgPSBjLkNsaWVudGVJRCAgKQ==” hover_enabled=”0″]LS0gMikgVXNhbmRvIE5PVCBFWElTVFMKU0VMRUNUIGMuQ2xpZW50ZUlELCBjLk5vbWUsIGMuU29icmVub21lCkZST00gZGJvLkNsaWVudGVzIGMKV0hFUkUgTk9UIEVYSVNUUwooICAgICAgU0VMRUNUIDEgRlJPTSBkYm8uVmVuZGFzIHYKV0hFUkUgdi5DbGllbnRlSUQgPSBjLkNsaWVudGVJRCAgKQ==[/et_pb_dmb_code_snippet]

Vejamos o que acontece quando utilizamos um LEFT OUTER JOIN.

[et_pb_dmb_code_snippet _builder_version=”4.0.6″ code=”LS0gMykgVXNhbmRvIExFRlQgT1VURVIgSk9JTgpTRUxFQ1QgYy5DbGllbnRlSUQsIGMuTm9tZSwgYy5Tb2JyZW5vbWUKRlJPTSBkYm8uQ2xpZW50ZXMgYwpMRUZUIEpPSU4gZGJvLlZlbmRhcyB2IE9OIHYuQ2xpZW50ZUlEID0gYy5DbGllbnRlSUQKV0hFUkUgdi5DbGllbnRlSUQgSVMgTlVMTA==” hover_enabled=”0″]LS0gMykgVXNhbmRvIExFRlQgT1VURVIgSk9JTgpTRUxFQ1QgYy5DbGllbnRlSUQsIGMuTm9tZSwgYy5Tb2JyZW5vbWUKRlJPTSBkYm8uQ2xpZW50ZXMgYwpMRUZUIEpPSU4gZGJvLlZlbmRhcyB2IE9OIHYuQ2xpZW50ZUlEID0gYy5DbGllbnRlSUQKV0hFUkUgdi5DbGllbnRlSUQgSVMgTlVMTA==[/et_pb_dmb_code_snippet]

O resultado da consulta é igual, mas o plano de execução gerado não é o mesmo. Veja que apesar de alcançarmos os mesmos resultados, explicitamente não aparece um Anti Semi Join quando usamos o LEFT OUTER JOIN. Se compararmos a performance das consultas veremos que o NOT EXISTS é mais eficiente. Por conta disso, deixo a seguinte dica: se você precisar recuperar linhas baseadas na não existência em outra tabela, prefira NOT EXISTS ou NOT IN; evite usar OUTER JOIN como foi demonstrado anteriormente.