Posicionar uma condição de filtro na cláusula incorreta de uma consulta JOIN pode gerar resultados errados silenciosamente, especialmente em outer joins. Este tópico explica como cada tipo de JOIN trata condições de filtro em subconsultas, na cláusula ON e na cláusula WHERE externa para que suas consultas retornem exatamente o esperado.
Tipos de JOIN suportados
O MaxCompute SQL suporta as seguintes operações JOIN.
|
Operação |
Descrição |
|
INNER JOIN |
Retorna linhas com valores correspondentes nas colunas de ambas as tabelas. |
|
LEFT JOIN |
Retorna todas as linhas da tabela à esquerda. Para linhas sem correspondência na tabela à direita, as colunas dessa tabela recebem valores NULL. |
|
RIGHT JOIN |
Retorna todas as linhas da tabela à direita. Para linhas sem correspondência na tabela à esquerda, as colunas dessa tabela recebem valores NULL. |
|
FULL JOIN |
Retorna todas as linhas de ambas as tabelas. Se uma linha não tiver correspondência na outra tabela, as colunas sem par recebem valores NULL. |
|
LEFT SEMI JOIN |
Retorna linhas da tabela à esquerda com pelo menos uma correspondência na tabela à direita. As linhas da tabela à direita não aparecem no resultado. |
|
LEFT ANTI JOIN |
Retorna linhas da tabela à esquerda sem nenhuma correspondência na tabela à direita. As linhas da tabela à direita não aparecem no resultado. Uso comum como substituto de |
Como o posicionamento do filtro afeta os resultados
Uma única instrução SQL pode combinar filtros de subconsulta, uma cláusula ON e uma cláusula WHERE externa:
SELECT *
FROM
(SELECT * FROM A WHERE <subquery_filter_A>) A
JOIN
(SELECT * FROM B WHERE <subquery_filter_B>) B
ON <on_condition>
WHERE <where_condition>
O MaxCompute avalia essas condições nesta ordem:
<subquery_filter>— condições WHERE dentro das subconsultas<on_condition>— a cláusula ON<where_condition>— a cláusula WHERE após o JOIN
Devido a essa ordem de avaliação, o mesmo filtro lógico pode produzir resultados diferentes dependendo do local onde for posicionado.
Em outer JOINs (LEFT JOIN, RIGHT JOIN, FULL JOIN), filtrar colunas da tabela fornecedora de nulos na cláusula WHERE externa elimina as linhas com valores NULL — justamente as linhas em que a tabela preservada não encontrou correspondência. Isso converte efetivamente o outer join em um INNER JOIN. Para manter o comportamento de outer join, posicione esses filtros na cláusula ON ou em uma subconsulta.
Terminologia de outer JOIN utilizada neste tópico:
|
Termo |
Definição |
Exemplos |
|
Tabela de linhas preservadas |
Tabela cujas linhas devem obrigatoriamente aparecer no resultado |
Tabela à esquerda no LEFT JOIN; tabela à direita no RIGHT JOIN; ambas as tabelas no FULL JOIN |
|
Tabela fornecedora de nulos |
Tabela que contribui com valores NULL para linhas sem correspondência |
Tabela à direita no LEFT JOIN; tabela à esquerda no RIGHT JOIN; ambas as tabelas no FULL JOIN |
Tabelas de teste
Os exemplos deste tópico utilizam duas tabelas, A e B.
Tabela A
CREATE TABLE A AS SELECT * FROM VALUES (1, 20180101),(2, 20180101),(2, 20180102) t (key, ds);
|
key |
ds |
|
1 |
20180101 |
|
2 |
20180101 |
|
2 |
20180102 |
Tabela B
CREATE TABLE B AS SELECT * FROM VALUES (1, 20180101),(3, 20180101),(2, 20180102) t (key, ds);
|
key |
ds |
|
1 |
20180101 |
|
3 |
20180101 |
|
2 |
20180102 |
O produto cartesiano de A e B possui 9 linhas. Ative consultas com produto cartesiano usando:
SET odps.sql.allow.cartesian=true;
SELECT * FROM A, B;
Resultado:
+------+----------+------+----------+
| key | ds | key2 | ds2 |
+------+----------+------+----------+
| 1 | 20180101 | 1 | 20180101 |
| 2 | 20180101 | 1 | 20180101 |
| 2 | 20180102 | 1 | 20180101 |
| 1 | 20180101 | 3 | 20180101 |
| 2 | 20180101 | 3 | 20180101 |
| 2 | 20180102 | 3 | 20180101 |
| 1 | 20180101 | 2 | 20180102 |
| 2 | 20180101 | 2 | 20180102 |
| 2 | 20180102 | 2 | 20180102 |
+------+----------+------+----------+
INNER JOIN
Um INNER JOIN retorna as linhas do produto cartesiano que satisfazem a condição de junção.
Resultado: O posicionamento da condição de filtro não altera o resultado.
Caso 1 — filtro na subconsulta:
SELECT A.*, B.*
FROM
(SELECT * FROM A WHERE ds='20180101') A
JOIN
(SELECT * FROM B WHERE ds='20180101') B
ON a.key = b.key;
Caso 2 — filtro na cláusula ON:
SELECT A.*, B.*
FROM A JOIN B
ON a.key = b.key AND A.ds='20180101' AND B.ds='20180101';
Caso 3 — filtro na cláusula WHERE externa:
SELECT A.*, B.*
FROM A JOIN B
ON a.key = b.key
WHERE A.ds='20180101' AND B.ds='20180101';
Os três casos retornam:
+------+----------+------+----------+
| key | ds | key2 | ds2 |
+------+----------+------+----------+
| 1 | 20180101 | 1 | 20180101 |
+------+----------+------+----------+
No INNER JOIN, o mecanismo de consulta aplica primeiro a condição de junção (gerando 3 linhas correspondentes a partir do produto cartesiano de 9 linhas) e depois aplica o filtro WHERE externo. Como ambas as etapas precisam ser satisfeitas, o resultado final é idêntico independentemente do posicionamento.
LEFT JOIN
Um LEFT JOIN retorna todas as linhas da tabela à esquerda (tabela de linhas preservadas). Linhas da tabela à esquerda sem correspondência na tabela à direita aparecem com valores NULL nas colunas da tabela à direita.
Resultado: O posicionamento da condição de filtro altera o resultado.
Atenção: Filtrar uma coluna da tabela à direita (fornecedora de nulos) na cláusula WHERE externa elimina todas as linhas com NULL — aquelas em que a tabela à esquerda não teve correspondência. Isso transforma o LEFT JOIN em um INNER JOIN na prática.
Para filtros da tabela à esquerda: filtro em subconsulta e filtro na cláusula WHERE externa produzem o mesmo resultado.
Para filtros da tabela à direita: filtro em subconsulta e filtro na cláusula ON produzem o mesmo resultado.
Caso 1 — filtro na subconsulta:
SELECT A.*, B.*
FROM
(SELECT * FROM A WHERE ds='20180101') A
LEFT JOIN
(SELECT * FROM B WHERE ds='20180101') B
ON a.key = b.key;
Resultado (2 linhas — linhas da tabela à esquerda com ds='20180101', com correspondência na tabela à direita ou NULL):
+------+----------+------+----------+
| key | ds | key2 | ds2 |
+------+----------+------+----------+
| 1 | 20180101 | 1 | 20180101 |
| 2 | 20180101 | NULL | NULL |
+------+----------+------+----------+
Caso 2 — filtro na cláusula ON:
SELECT A.*, B.*
FROM A LEFT JOIN B
ON a.key = b.key AND A.ds='20180101' AND B.ds='20180101';
A cláusula ON filtra quais linhas de B podem corresponder, mas todas as linhas de A continuam preservadas. Do produto cartesiano de 9 linhas, apenas 1 par corresponde. As 2 linhas de A sem correspondência retornam NULL nas colunas de B.
Resultado (3 linhas — todas as linhas de A, com correspondência ou NULL):
+------+----------+------+----------+
| key | ds | key2 | ds2 |
+------+----------+------+----------+
| 1 | 20180101 | 1 | 20180101 |
| 2 | 20180101 | NULL | NULL |
| 2 | 20180102 | NULL | NULL |
+------+----------+------+----------+
Caso 3 — filtro na cláusula WHERE externa:
SELECT A.*, B.*
FROM A LEFT JOIN B
ON a.key = b.key
WHERE A.ds='20180101' AND B.ds='20180101';
A junção gera linhas com NULL em B.ds para as linhas de A sem correspondência. O filtro WHERE B.ds='20180101' não consegue corresponder a NULL, eliminando essas linhas.
Resultado (1 linha — igual ao INNER JOIN):
+------+----------+------+----------+
| key | ds | key2 | ds2 |
+------+----------+------+----------+
| 1 | 20180101 | 1 | 20180101 |
+------+----------+------+----------+
RIGHT JOIN
O RIGHT JOIN espelha o LEFT JOIN com as tabelas invertidas. Todas as linhas da tabela à direita (tabela de linhas preservadas) são retornadas, e as colunas da tabela à esquerda recebem NULL quando não há correspondência.
Resultado: O posicionamento da condição de filtro altera o resultado.
Para filtros da tabela à direita: filtro em subconsulta e filtro na cláusula WHERE externa produzem o mesmo resultado.
Para filtros da tabela à esquerda: filtro em subconsulta e filtro na cláusula ON produzem o mesmo resultado.
A mesma armadilha se aplica: filtrar uma coluna da tabela à esquerda (fornecedora de nulos) na cláusula WHERE externa elimina as linhas com NULL, convertendo efetivamente o RIGHT JOIN em um INNER JOIN.
FULL JOIN
Um FULL JOIN retorna todas as linhas de ambas as tabelas. Ambas atuam simultaneamente como tabelas de linhas preservadas e fornecedoras de nulos. Quando uma linha não encontra correspondência, valores NULL preenchem as colunas sem par.
Resultado: O posicionamento da condição de filtro altera o resultado.
Atenção: No FULL JOIN, filtros na cláusula ON ou na cláusula WHERE externa não restringem quais linhas são retornadas de cada tabela — eles afetam apenas se as correspondências são encontradas. Somente filtros em subconsultas restringem a entrada de forma confiável antes da junção.
Caso 1 — filtro na subconsulta:
SELECT A.*, B.*
FROM
(SELECT * FROM A WHERE ds='20180101') A
FULL JOIN
(SELECT * FROM B WHERE ds='20180101') B
ON a.key = b.key;
Resultado (3 linhas):
+------+----------+------+----------+
| key | ds | key2 | ds2 |
+------+----------+------+----------+
| 2 | 20180101 | NULL | NULL |
| 1 | 20180101 | 1 | 20180101 |
| NULL | NULL | 3 | 20180101 |
+------+----------+------+----------+
Caso 2 — filtro na cláusula ON:
SELECT A.*, B.*
FROM A FULL JOIN B
ON a.key = b.key AND A.ds='20180101' AND B.ds='20180101';
Apenas 1 linha do produto cartesiano de 9 linhas atende à condição de junção. As 2 linhas de A sem correspondência recebem NULL nas colunas de B, e as 2 linhas de B sem correspondência recebem NULL nas colunas de A.
Resultado (5 linhas):
+------+----------+------+----------+
| key | ds | key2 | ds2 |
+------+----------+------+----------+
| NULL | NULL | 2 | 20180102 |
| 2 | 20180101 | NULL | NULL |
| 2 | 20180102 | NULL | NULL |
| 1 | 20180101 | 1 | 20180101 |
| NULL | NULL | 3 | 20180101 |
+------+----------+------+----------+
Caso 3 — filtro na cláusula WHERE externa:
SELECT A.*, B.*
FROM A FULL JOIN B
ON a.key = b.key
WHERE A.ds='20180101' AND B.ds='20180101';
A junção produz 4 linhas (3 correspondentes + 1 linha de B sem correspondência com colunas de A em NULL). O filtro WHERE elimina as linhas em que qualquer coluna seja NULL.
Resultado (1 linha):
+------+----------+------+----------+
| key | ds | key2 | ds2 |
+------+----------+------+----------+
| 1 | 20180101 | 1 | 20180101 |
+------+----------+------+----------+
LEFT SEMI JOIN
Um LEFT SEMI JOIN retorna linhas da tabela à esquerda que possuem pelo menos uma correspondência na tabela à direita. Como as linhas da tabela à direita não fazem parte do resultado, suas colunas não podem ser referenciadas na cláusula WHERE externa.
Resultado: O posicionamento da condição de filtro não altera o resultado.
Caso 1 — filtro na subconsulta:
SELECT A.*
FROM
(SELECT * FROM A WHERE ds='20180101') A
LEFT SEMI JOIN
(SELECT * FROM B WHERE ds='20180101') B
ON a.key = b.key;
Caso 2 — filtro na cláusula ON:
SELECT A.*
FROM A LEFT SEMI JOIN B
ON a.key = b.key AND A.ds='20180101' AND B.ds='20180101';
Caso 3 — filtro na cláusula WHERE externa (filtro da tabela à direita deve estar na subconsulta):
SELECT A.*
FROM A LEFT SEMI JOIN
(SELECT * FROM B WHERE ds='20180101') B
ON a.key = b.key
WHERE A.ds='20180101';
Os três casos retornam:
+------+----------+
| key | ds |
+------+----------+
| 1 | 20180101 |
+------+----------+
LEFT ANTI JOIN
Um LEFT ANTI JOIN retorna linhas da tabela à esquerda que não possuem correspondência na tabela à direita. Como as linhas da tabela à direita não fazem parte do resultado, suas colunas não podem ser referenciadas na cláusula WHERE externa.
Resultado: O posicionamento da condição de filtro altera o resultado.
Para filtros da tabela à esquerda: filtro em subconsulta e filtro na cláusula WHERE externa produzem o mesmo resultado.
Para filtros da tabela à direita: filtro em subconsulta e filtro na cláusula ON produzem o mesmo resultado.
Caso 1 — filtro na subconsulta:
SELECT A.*
FROM
(SELECT * FROM A WHERE ds='20180101') A
LEFT ANTI JOIN
(SELECT * FROM B WHERE ds='20180101') B
ON a.key = b.key;
Resultado (1 linha):
+------+----------+
| key | ds |
+------+----------+
| 2 | 20180101 |
+------+----------+
Caso 2 — filtro na cláusula ON:
SELECT A.*
FROM A LEFT ANTI JOIN B
ON a.key = b.key AND A.ds='20180101' AND B.ds='20180101';
A cláusula ON restringe quais linhas de B podem servir como correspondência. Com uma condição de junção mais restritiva, mais linhas de A ficam sem par e são retornadas.
Resultado (2 linhas):
+------+----------+
| key | ds |
+------+----------+
| 2 | 20180101 |
| 2 | 20180102 |
+------+----------+
Caso 3 — filtro na cláusula WHERE externa (filtro da tabela à direita deve estar na subconsulta):
SELECT A.*
FROM A LEFT ANTI JOIN
(SELECT * FROM B WHERE ds='20180101') B
ON a.key = b.key
WHERE A.ds='20180101';
A junção retorna 2 linhas e, em seguida, o filtro WHERE externo reduz para 1.
Resultado (1 linha):
+------+----------+
| key | ds |
+------+----------+
| 2 | 20180101 |
+------+----------+
Referência de posicionamento de filtros
Comportamento por tipo de JOIN
|
Tipo de JOIN |
Filtro da tabela à esquerda |
Filtro da tabela à direita |
|
INNER JOIN |
Qualquer posição gera o mesmo resultado |
Qualquer posição gera o mesmo resultado |
|
LEFT SEMI JOIN |
Qualquer posição gera o mesmo resultado |
Qualquer posição gera o mesmo resultado |
|
LEFT JOIN |
Subconsulta = WHERE externa |
Subconsulta = cláusula ON |
|
LEFT ANTI JOIN |
Subconsulta = WHERE externa |
Subconsulta = cláusula ON |
|
RIGHT JOIN |
Subconsulta = cláusula ON |
Subconsulta = WHERE externa |
|
FULL JOIN |
Apenas subconsulta |
Apenas subconsulta |
Recomendações
Utilize filtros em subconsultas para outer JOINs. Filtros em subconsultas restringem os dados de entrada antes da execução da junção, preservando intacta a semântica do join. Essa é a abordagem mais segura para todos os tipos de JOIN.
Se você usar a cláusula ON para filtros da tabela fornecedora de nulos em um LEFT JOIN ou RIGHT JOIN, a semântica de outer join será mantida. Colocar esses filtros na cláusula WHERE externa elimina as linhas com NULL e converte efetivamente o outer join em um INNER JOIN.
No FULL JOIN, filtros em subconsultas são a única opção que restringe a entrada de forma confiável — filtros na cláusula ON ou na cláusula WHERE externa alteram quais linhas correspondem, mas não excluem linhas sem correspondência de nenhuma das tabelas.
Próximos passos
JOIN — referência de sintaxe para operações JOIN padrão no MaxCompute SQL
SEMI JOIN — referência de sintaxe para LEFT SEMI JOIN e LEFT ANTI JOIN
MAPJOIN HINT — melhore o desempenho ao juntar uma tabela grande com uma tabela pequena
DISTRIBUTED MAPJOIN — melhore o desempenho ao juntar uma tabela grande com uma tabela de tamanho médio
SKEWJOIN HINT — lide com valores de chave quente que causam problemas de longa duração em operações JOIN