Junção interna, junção externa, junção cruzada? O que da?
É uma pergunta válida. Certa vez vi um código Visual Basic com códigos T-SQL embutidos nele. O código VB recupera registros de tabela com várias instruções SELECT, um SELECT * por tabela. Em seguida, ele combina vários conjuntos de resultados em um conjunto de registros. Absurdo?
Para os jovens desenvolvedores que fizeram isso, não foi. Mas quando me pediram para avaliar por que o sistema estava lento, essa questão foi a primeira a chamar minha atenção. Está certo. Eles nunca ouviram falar de junções SQL. Para ser justo com eles, eles foram honestos e abertos a sugestões.
Como você descreve as junções SQL? Talvez você se lembre de uma música - Imagine por John Lennon:
Você pode dizer que sou um sonhador, mas não sou o único.
Espero que um dia você se junte a nós e que o mundo seja um só.
No contexto da música, unir é unir. Em um banco de dados SQL, combinar registros de 2 ou mais tabelas em um conjunto de resultados forma uma junção .
Este artigo é o início de uma série de 3 partes falando sobre junções SQL:
- INNER JOIN
- OUTER JOIN, que inclui LEFT, RIGHT e FULL
- CROSS JOIN
Mas antes de começarmos a discutir o INNER JOIN, vamos descrever as junções em geral.
Mais sobre SQL JOIN
As junções aparecem logo após a cláusula FROM. Em sua forma mais simples, parece usar o padrão SQL-92:
FROM <table source> [<alias1>]
<join type> JOIN <table source> [<alias2>] [ON <join condition>]
[<join type> JOIN <table source> [<alias3>] [ON <join condition>]
<join type> JOIN <table source> [<aliasN>] [ON <join condition>]]
[WHERE <condition>]
Vamos descrever as coisas cotidianas em torno do JOIN.
Fontes de tabela
Você pode adicionar até 256 fontes de tabelas, de acordo com a Microsoft. Claro, isso depende dos recursos do seu servidor. Eu nunca juntei mais de 10 tabelas na minha vida, sem falar em 256. De qualquer forma, fontes de tabela podem ser qualquer uma das seguintes:
- Tabela
- Visualizar
- Sinônimo de tabela ou visualização
- Variável de tabela
- Função com valor de tabela
- Tabela derivada
Alias da tabela
Um alias é opcional, mas encurta seu código e minimiza a digitação. Também ajuda a evitar erros quando existe um nome de coluna em duas ou mais tabelas usadas em um SELECT, UPDATE, INSERT ou DELETE. Também adiciona clareza ao seu código. É opcional, mas eu recomendo usar aliases. (A menos que você goste de digitar fontes de tabela pelo nome.)
Condição de adesão
A palavra-chave ON precede a condição de junção que pode ser uma única junção ou colunas de 2 chaves das 2 tabelas unidas. Ou pode ser uma junção composta usando mais de 2 colunas de chave. Ele define como as tabelas estão relacionadas.
No entanto, usamos a condição de junção apenas para junções INNER e OUTER. Usá-lo em um CROSS JOIN acionará um erro.
Como as condições de junção definem os relacionamentos, elas precisam de operadores.
O mais comum dos operadores de condição de junção é o operador de igualdade (=). Outros operadores como> ou
SQL JOIN vs. Subconsultas
A maioria das junções pode ser reescrita como subconsultas e vice-versa. Confira este artigo para saber mais sobre subconsultas em comparação com junções.
Uniões e tabelas derivadas
O uso de tabelas derivadas em uma junção se parece com isso:
FROM table1 a
INNER JOIN (SELECT y.column3 from table2 x
INNER JOIN table3 y on x.column1 = y.column1) b ON a.col1 = b.col2
Ele está se juntando a partir do resultado de outra instrução SELECT e é perfeitamente válido.
Você terá mais exemplos, mas vamos lidar com uma última coisa sobre SQL JOINS. É como os processos do Query Optimizer do SQL Server se unem.
Como o SQL Server processa junções
Para entender como o processo funciona, você precisa conhecer os dois tipos de operações envolvidas:
- Operações lógicas correspondem aos tipos de junções usados em uma consulta:INNER, OUTER ou CROSS. Você, como desenvolvedor, define essa parte do processamento ao formar a consulta.
- Operações físicas – o Query Optimizer escolhe a melhor operação física aplicável à sua junção. O melhor significa o mais rápido para produzir resultados. O Plano de Execução de sua consulta mostrará os operadores de junção física escolhidos. Essas operações são:
- Unir Loop Aninhado. Esta operação é rápida se uma das duas tabelas for pequena e a segunda for grande e indexada. Requer menos E/S com menos comparações, mas não é bom para grandes conjuntos de resultados.
- Mesclar associação. Essa é a operação mais rápida para conjuntos de resultados grandes e classificados por colunas usadas na junção.
- Juntar com hash. O otimizador de consulta o usa quando o conjunto de resultados é muito grande para um loop aninhado e as entradas não são classificadas para uma junção de mesclagem. Um hash é mais eficiente do que classificá-lo primeiro e aplicar uma junção de mesclagem.
- União adaptável. A partir do SQL Server 2017, ele permite a escolha entre um loop aninhado ou hash . O método de junção é adiado até que a primeira entrada seja verificada. Essa operação alterna dinamicamente para uma junção física melhor sem recompilar.
Por que precisamos nos preocupar com isso?
Uma palavra:Desempenho.
Uma coisa é saber como formar consultas com junções para produzir resultados corretos. Outra é fazê-lo funcionar o mais rápido possível. Você precisa estar mais preocupado com isso se quiser uma boa reputação com seus usuários.
Então, o que você precisa observar no Plano de Execução para essas operações lógicas?
- Suponha que um operador de classificação preceda a junção de mesclagem . Essa operação de classificação é cara para tabelas grandes (Figura 2). Você pode corrigir isso pré-classificando as tabelas de entrada na junção.
- Suponha que haja duplicatas nas tabelas de entrada de uma junção de mesclagem . O SQL Server gravará as duplicatas da segunda tabela em uma WorkTable em tempdb. Então, ele vai fazer as comparações lá. O STATISTICS IO revelará todas as WorkTables envolvidas.
- Quando grandes volumes de dados chegam ao tempdb em um Hash jo in, o STATISTICS IO revelará uma grande leitura lógica em WorkFiles ou WorkTables. Um aviso também aparecerá no Plano de Execução (Figura 3). Você pode aplicar duas coisas:pré-classificar as tabelas de entrada ou diminuir as junções, se possível. Como resultado, o Otimizador de consulta pode escolher outra junção física.
Dicas de participação
As dicas de associação são novas no SQL Server 2019. Quando você as usa em suas associações, ela informa ao otimizador de consulta para parar de decidir o que é melhor para a consulta. Você é o chefe quando se trata da junção física a ser usada.
Agora pare, bem aí. A verdade é que o otimizador de consulta normalmente seleciona a melhor junção física para sua consulta. Se você não sabe o que está fazendo, não use dicas de junção.
As dicas possíveis que você pode especificar são LOOP, MERGE, HASH ou REMOTE.
Eu não usei dicas de junção, mas aqui está a sintaxe:
<join type> <join hint> JOIN <table source> [<alias>] ON <join condition>
Tudo sobre INNER JOIN
INNER JOIN retorna as linhas com registros correspondentes em ambas as tabelas, com base em uma condição. Também é a junção padrão se você não especificar a palavra-chave INNER:
Como você vê, as linhas correspondentes da Tabela1 e Tabela2 são retornados usando Key1 como a condição de junção. A Tabela1 registro com Key1 ='C' foi excluído porque não há registros correspondentes na Tabela2 .
Sempre que faço uma consulta, minha primeira opção é INNER JOIN. OUTER JOIN vem apenas quando os requisitos o determinam.
Sintaxe de INNER JOIN
Há duas sintaxes INNER JOIN com suporte no T-SQL:SQL-92 e SQL-89.
SQL-92 INNER JOIN
FROM <table source1> [<alias1>]
INNER JOIN <table source2> [<alias2>] ON <join condition1>
[INNER JOIN <table source3> [<alias3>] ON <join condition2>
INNER JOIN <table sourceN> [<aliasN>] ON <join conditionN>]
[WHERE <condition>]
SQL-89 INNER JOIN
FROM <table source1> [alias1], <table source2> [alias2] [, <table source3> [alias3], <table sourceN> [aliasN]]
WHERE (<join condition1>)
[AND (<join condition2>)
AND (<join condition3>)
AND (<join conditionN>)]
Qual sintaxe de INNER JOIN é melhor?
A primeira sintaxe de junção que aprendi foi SQL-89. Quando o SQL-92 finalmente chegou, achei que era muito longo. Eu também pensei que a saída era a mesma, por que se incomodar em digitar mais palavras-chave? Um designer de consulta gráfica gerou o código SQL-92 e eu o alterei de volta para SQL-89. Mas hoje, prefiro SQL-92 mesmo que tenha que digitar mais. Aqui está o porquê:
- A intenção do tipo de junção é clara. A próxima pessoa que manter meu código saberá o que se pretende na consulta.
- Esquecer a condição de junção em uma sintaxe SQL-92 acionará um erro. Enquanto isso, esquecer a condição de junção no SQL-89 será tratado como um CROSS JOIN. Se eu quis dizer uma junção INNER ou OUTER, seria um bug de lógica imperceptível até que os usuários reclamassem.
- As novas ferramentas estão mais inclinadas ao SQL-92. Se eu usar um designer gráfico de consulta novamente, não precisarei alterá-lo para SQL-89. Não sou mais teimosa, então minha frequência cardíaca voltou ao normal. Saudades de mim.
Os motivos acima são meus. Você pode ter suas razões pelas quais prefere o SQL-92 ou por que o odeia. Eu me pergunto quais são esses motivos. Deixe-me saber na seção de comentários abaixo.
Mas não podemos terminar este artigo sem exemplos e explicações.
10 exemplos de INNER JOIN
1. Juntando 2 mesas
Aqui está um exemplo de 2 tabelas unidas usando INNER JOIN na sintaxe SQL-92.
-- Display Vests, Helmets, and Light products
USE AdventureWorks
GO
SELECT
p.ProductID
,P.Name AS [Product]
,ps.ProductSubcategoryID
,ps.Name AS [ProductSubCategory]
FROM Production.Product p
INNER JOIN Production.ProductSubcategory ps ON P.ProductSubcategoryID = ps.ProductSubcategoryID
WHERE P.ProductSubcategoryID IN (25, 31, 33); -- for vest, helmet, and light
-- product subcategories
Você especifica apenas as colunas de que precisa. No exemplo acima, 4 colunas são especificadas. Eu sei que é muito longo do que SELECT * mas tenha isso em mente:é a melhor prática.
Observe também o uso de aliases de tabela. Tanto o Produto e ProductSubcategory as tabelas têm uma coluna chamada [Nome ]. Se você não especificar o alias, um erro será acionado.
Enquanto isso, aqui está a sintaxe SQL-89 equivalente:
-- Display Vests, Helmets, and Light products
USE AdventureWorks
GO
SELECT
p.ProductID
,P.Name AS [Product]
,ps.ProductSubcategoryID
,ps.Name AS [ProductSubCategory]
FROM Production.Product p, Production.ProductSubcategory ps
WHERE P.ProductSubcategoryID = ps.ProductSubcategoryID
AND P.ProductSubcategoryID IN (25, 31, 33);
Eles são os mesmos, exceto pela condição de junção misturada na cláusula WHERE com uma palavra-chave AND. Mas sob o capô, eles são realmente os mesmos? Vamos inspecionar o conjunto de resultados, o STATISTICS IO e o Plano de Execução.
Veja o conjunto de resultados de 9 registros:
Não são apenas os resultados, mas os recursos exigidos pelo SQL Server também são os mesmos.
Veja as leituras lógicas:
Por fim, o Plano de Execução revela o mesmo plano de consulta para ambas as consultas quando seus QueryPlanHashes são iguais. Observe também as operações destacadas no diagrama:
Com base nas descobertas, o processamento de consultas do SQL Server é o mesmo, seja SQL-92 ou SQL-89. Mas como eu disse, a clareza no SQL-92 é muito melhor para mim.
A Figura 7 também mostra uma Junção de Loop Aninhada usada no plano. Por quê? O conjunto de resultados é pequeno.
2. Unindo várias tabelas
Confira a consulta abaixo usando 3 tabelas unidas.
-- Get the total number of orders per Product Category
USE AdventureWorks
GO
SELECT
ps.Name AS ProductSubcategory
,SUM(sod.OrderQty) AS TotalOrders
FROM Production.Product p
INNER JOIN Sales.SalesOrderDetail sod ON P.ProductID = sod.ProductID
INNER JOIN Sales.SalesOrderHeader soh ON sod.SalesOrderID = soh.SalesOrderID
INNER JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID
WHERE soh.OrderDate BETWEEN '1/1/2014' AND '12/31/2014'
AND p.ProductSubcategoryID IN (1,2)
GROUP BY ps.Name
HAVING ps.Name IN ('Mountain Bikes', 'Road Bikes')
3. União composta
Você também pode juntar 2 tabelas usando 2 chaves para relacioná-lo. Confira a amostra abaixo. Ele usa 2 condições de junção com um operador AND.
SELECT
a.column1
,b.column1
,b.column2
FROM Table1 a
INNER JOIN Table2 b ON a.column1 = b.column1 AND a.column2 = b.column2
4. INNER JOIN usando uma junção física de loop aninhado
No exemplo abaixo, o Produto tabela tem 9 registros – um pequeno conjunto. A tabela unida é SalesOrderDetail – um grande conjunto. O Query Optimizer usará uma junção de loop aninhada, conforme mostrado na Figura 8.
USE AdventureWorks
GO
SELECT
sod.SalesOrderDetailID
,sod.ProductID
,P.Name
,P.ProductNumber
,sod.OrderQty
FROM Sales.SalesOrderDetail sod
INNER JOIN Production.Product p ON sod.ProductID = p.ProductID
WHERE P.ProductSubcategoryID IN(25, 31, 33);
5. INNER JOIN usando uma junção física de mesclagem
O exemplo abaixo usa um Merge Join porque ambas as tabelas de entrada são classificadas por SalesOrderID.
SELECT
soh.SalesOrderID
,soh.OrderDate
,sod.SalesOrderDetailID
,sod.ProductID
,sod.LineTotal
FROM Sales.SalesOrderHeader soh
INNER JOIN sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID
6. INNER JOIN usando uma junção física de hash
O exemplo a seguir usará um Hash Join:
SELECT
s.Name AS Store
,SUM(soh.TotalDue) AS TotalSales
FROM Sales.SalesOrderHeader soh
INNER JOIN Sales.Store s ON soh.SalesPersonID = s.SalesPersonID
GROUP BY s.Name
7. INNER JOIN usando Adaptive Physical Join
No exemplo abaixo, o SalesPerson A tabela tem um índice ColumnStore não clusterizado no TerritoryID coluna. O Query Optimizer decidiu por uma junção de loop aninhada, conforme mostrado na Figura 11.
SELECT
sp.BusinessEntityID
,sp.SalesQuota
,st.Name AS Territory
FROM Sales.SalesPerson sp
INNER JOIN Sales.SalesTerritory st ON sp.TerritoryID = st.TerritoryID
WHERE sp.TerritoryID BETWEEN 1 AND 5
8. Duas maneiras de reescrever uma subconsulta para um INNER JOIN
Considere esta instrução com uma subconsulta aninhada:
SELECT [SalesOrderID], [OrderDate], [ShipDate], [CustomerID]
FROM Sales.SalesOrderHeader
WHERE [CustomerID] IN (SELECT [CustomerID] FROM Sales.Customer
WHERE PersonID IN (SELECT BusinessEntityID FROM Person.Person
WHERE lastname LIKE N'I%' AND PersonType='SC'))
Os mesmos resultados podem sair se você alterá-lo para um INNER JOIN, conforme abaixo:
SELECT o.[SalesOrderID], o.[OrderDate], o.[ShipDate], o.[CustomerID]
FROM Sales.SalesOrderHeader o
INNER JOIN Sales.Customer c on o.CustomerID = c.CustomerID
INNER JOIN Person.Person p ON c.PersonID = p.BusinessEntityID
WHERE p.PersonType = 'SC'
AND p.lastname LIKE N'I%'
Outra maneira de reescrevê-lo é usando uma tabela derivada como fonte de tabela para o INNER JOIN:
SELECT o.[SalesOrderID], o.[OrderDate], o.[ShipDate], o.[CustomerID]
FROM Sales.SalesOrderHeader o
INNER JOIN (SELECT c.CustomerID, P.PersonType, P.LastName
FROM Sales.Customer c
INNER JOIN Person.Person p ON c.PersonID = P.BusinessEntityID
WHERE p.PersonType = 'SC'
AND p.LastName LIKE N'I%') AS q ON o.CustomerID = q.CustomerID
Todas as 3 consultas geram os mesmos 48 registros.
9. Usando dicas de junção
A consulta a seguir usa um loop aninhado:
SELECT
sod.SalesOrderDetailID
,sod.ProductID
,P.Name
,P.ProductNumber
,sod.OrderQty
FROM Sales.SalesOrderDetail sod
INNER JOIN Production.Product p ON sod.ProductID = p.ProductID
WHERE P.ProductSubcategoryID IN(25, 31, 33);
Se você quiser forçá-lo a uma junção de hash, é isso que acontece:
SELECT
sod.SalesOrderDetailID
,sod.ProductID
,P.Name
,P.ProductNumber
,sod.OrderQty
FROM Sales.SalesOrderDetail sod
INNER HASH JOIN Production.Product p ON sod.ProductID = p.ProductID
WHERE P.ProductSubcategoryID IN(25, 31, 33);
No entanto, observe que STATISTICS IO mostra que o desempenho ficará ruim quando você o forçar a uma junção de hash.
Enquanto isso, a consulta abaixo usa um Merge Join:
SELECT
soh.SalesOrderID
,soh.OrderDate
,sod.SalesOrderDetailID
,sod.ProductID
,sod.LineTotal
FROM Sales.SalesOrderHeader soh
INNER JOIN sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID
Aqui está o que se torna quando você o força para um loop aninhado:
SELECT
soh.SalesOrderID
,soh.OrderDate
,sod.SalesOrderDetailID
,sod.ProductID
,sod.LineTotal
FROM Sales.SalesOrderHeader soh
INNER LOOP JOIN sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID
Ao verificar o STATISTICS IO de ambos, forçá-lo a um Nested Loop requer mais recursos para processar a consulta:
Assim, o uso de dicas de junção deve ser seu último recurso ao ajustar o desempenho. Deixe seu SQL Server lidar com isso para você.
10. Usando INNER JOIN em UPDATE
Você também pode usar INNER JOIN em uma instrução UPDATE. Aqui está um exemplo:
UPDATE Sales.SalesOrderHeader
SET ShipDate = getdate()
FROM Sales.SalesOrderHeader o
INNER JOIN Sales.Customer c on o.CustomerID = c.CustomerID
INNER JOIN Person.Person p ON c.PersonID = p.BusinessEntityID
WHERE p.PersonType = 'SC'
Já que é possível usar um join em um UPDATE, por que não tentar usando DELETE e INSERT?
Resumo do SQL Join e do INNER JOIN
Então, qual é o grande problema da junção SQL?
- Um SQL JOIN combina registros de 2 ou mais tabelas para formar um conjunto de resultados.
- Existem tipos de junções no SQL:INNER, OUTER e CROSS.
- Como desenvolvedor ou administrador, você decide quais operações lógicas ou tipos de junção usar para atender aos seus requisitos.
- Por outro lado, o Otimizador de Consultas decide os melhores operadores de junção física a serem usados. Pode ser loop aninhado, mesclar, hash ou adaptável.
- Você pode usar dicas de junção para forçar qual junção física usar, mas deve ser seu último recurso. Na maioria dos casos, é melhor deixar o SQL Server lidar com isso.
- Conhecer os operadores de junção física também ajuda a ajustar o desempenho da consulta.
- Além disso, as subconsultas podem ser reescritas usando junções.
Enquanto isso, este post mostrou 10 exemplos de INNER JOINs. Não são apenas códigos de amostra. Alguns deles também incluem uma inspeção de como o código funciona de dentro para fora. Não é apenas para ajudá-lo a codificar, mas para ajudá-lo a estar atento ao desempenho. No final do dia, os resultados não devem ser apenas corretos, mas também entregues rapidamente.
Ainda não terminamos. O próximo artigo tratará de OUTER JOINS. Fique atento.
Veja também
As junções SQL permitem buscar e combinar dados de mais de uma tabela. Assista a este vídeo para saber mais sobre junções SQL.