Tipos de junções SQL
1. Introdução
Neste tutorial, mostraremos diferentes tipos de junções SQL e como eles podem ser facilmente implementados em Java.
2. Definindo o modelo
Vamos começar criando duas tabelas simples:
CREATE TABLE AUTHOR
(
ID int NOT NULL PRIMARY KEY,
FIRST_NAME varchar(255),
LAST_NAME varchar(255)
);
CREATE TABLE ARTICLE
(
ID int NOT NULL PRIMARY KEY,
TITLE varchar(255) NOT NULL,
AUTHOR_ID int,
FOREIGN KEY(AUTHOR_ID) REFERENCES AUTHOR(ID)
);
E preencha-os com alguns dados de teste:
INSERT INTO AUTHOR VALUES
(1, 'Siena', 'Kerr'),
(2, 'Daniele', 'Ferguson'),
(3, 'Luciano', 'Wise'),
(4, 'Jonas', 'Lugo');
INSERT INTO ARTICLE VALUES
(1, 'First steps in Java', 1),
(2, 'SpringBoot tutorial', 1),
(3, 'Java 12 insights', null),
(4, 'SQL JOINS', 2),
(5, 'Introduction to Spring Security', 3);
Observe que em nosso conjunto de dados de amostra, nem todos os autores têm artigos e vice-versa. Isso terá um papel importante em nossos exemplos, que veremos mais tarde.
Vamos também definir um POJO que usaremos para armazenar os resultados das operações JOIN em todo o nosso tutorial:
class ArticleWithAuthor {
private String title;
private String authorFirstName;
private String authorLastName;
// standard constructor, setters and getters
}
Em nossos exemplos, extrairemos um título da tabela ARTICLE e os dados dos autores da tabela AUTHOR.
3. Configuração
Para nossos exemplos, usaremos um banco de dados PostgreSQL externo em execução na porta 5432. Apart from the FULL JOIN, which is not supported in either MySQL or H2, all provided snippets should work with any SQL provider.
Para a nossa implementação Java, precisamos de umPostgreSQL driver:
org.postgresql
postgresql
42.2.5
test
Vamos primeiro configurar umjava.sql.Connection para trabalhar com nosso banco de dados:
Class.forName("org.postgresql.Driver");
Connection connection = DriverManager.
getConnection("jdbc:postgresql://localhost:5432/myDb", "user", "pass");
A seguir, vamos criar uma classe DAO e alguns métodos utilitários:
class ArticleWithAuthorDAO {
private final Connection connection;
// constructor
private List executeQuery(String query) {
try (Statement statement = connection.createStatement()) {
ResultSet resultSet = statement.executeQuery(query);
return mapToList(resultSet);
} catch (SQLException e) {
e.printStackTrace();
}
return new ArrayList<>();
}
private List mapToList(ResultSet resultSet) throws SQLException {
List list = new ArrayList<>();
while (resultSet.next()) {
ArticleWithAuthor articleWithAuthor = new ArticleWithAuthor(
resultSet.getString("TITLE"),
resultSet.getString("FIRST_NAME"),
resultSet.getString("LAST_NAME")
);
list.add(articleWithAuthor);
}
return list;
}
}
Neste artigo, não entraremos em detalhes sobre como usarResultSet, Statement,eConnection.. Esses tópicos são abordados em nossos artigos relacionados deJDBC.
Vamos começar a explorar as junções SQL nas seções abaixo.
4. Junção interna
Vamos começar com possivelmente o tipo mais simples de junção. O INNER JOIN é uma operação que seleciona linhas que correspondem a uma condição fornecida nas duas tabelas. A consulta consiste em pelo menos três partes: selecione colunas, junte tabelas e junte condição.
Tendo isso em mente, a sintaxe em si se torna bastante direta:
SELECT ARTICLE.TITLE, AUTHOR.LAST_NAME, AUTHOR.FIRST_NAME
FROM ARTICLE INNER JOIN AUTHOR
ON AUTHOR.ID=ARTICLE.AUTHOR_ID
Podemos também ilustrar o resultado deINNER JOIN as a common part of intersecting sets:
Vamos agora implementar o método para INNER JOIN na classeArticleWithAuthorDAO:
List articleInnerJoinAuthor() {
String query = "SELECT ARTICLE.TITLE, AUTHOR.LAST_NAME, AUTHOR.FIRST_NAME "
+ "FROM ARTICLE INNER JOIN AUTHOR ON AUTHOR.ID=ARTICLE.AUTHOR_ID";
return executeQuery(query);
}
E teste:
@Test
public void whenQueryWithInnerJoin_thenShouldReturnProperRows() {
List articleWithAuthorList = articleWithAuthorDAO.articleInnerJoinAuthor();
assertThat(articleWithAuthorList).hasSize(4);
assertThat(articleWithAuthorList)
.noneMatch(row -> row.getAuthorFirstName() == null || row.getTitle() == null);
}
Como mencionamos anteriormente, o INNER JOIN seleciona apenas linhas comuns por uma condição fornecida. Observando nossas inserções, vemos que temos um artigo sem autor e um autor sem artigo. Essas linhas são ignoradas porque não atendem à condição fornecida. Como resultado, recuperamos quatro resultados unidos e nenhum deles possui dados vazios de autores nem título vazio.
5. Associação à esquerda
A seguir, vamos nos concentrar no LEFT JOIN. Esse tipo de junção seleciona todas as linhas da primeira tabela e corresponde às linhas correspondentes da segunda tabela. Pois quando não há correspondência, as colunas são preenchidas com valoresnull.
Antes de mergulharmos na implementação do Java, vamos dar uma olhada em uma representação gráfica do LEFT JOIN:
Neste caso, o resultado doLEFT JOIN includes every record from the set representing the first table with intersecting values from the second table.
Agora, vamos passar para a implementação Java:
List articleLeftJoinAuthor() {
String query = "SELECT ARTICLE.TITLE, AUTHOR.LAST_NAME, AUTHOR.FIRST_NAME "
+ "FROM ARTICLE LEFT JOIN AUTHOR ON AUTHOR.ID=ARTICLE.AUTHOR_ID";
return executeQuery(query);
}
A única diferença para o exemplo anterior é que usamos a palavra-chave LEFT em vez da palavra-chave INNER.
Antes de testar nosso método LEFT JOIN, vamos novamente dar uma olhada em nossas inserções. Nesse caso, receberemos todos os registros da tabela ARTICLE e suas linhas correspondentes da tabela AUTHOR. Como mencionamos antes, nem todo artigo tem um autor ainda, então esperamos ter valores denull no lugar dos dados do autor:
@Test
public void whenQueryWithLeftJoin_thenShouldReturnProperRows() {
List articleWithAuthorList = articleWithAuthorDAO.articleLeftJoinAuthor();
assertThat(articleWithAuthorList).hasSize(5);
assertThat(articleWithAuthorList).anyMatch(row -> row.getAuthorFirstName() == null);
}
6. Right Join
The RIGHT JOIN is much like the LEFT JOIN, but it returns all rows from the second table and matches rows from the first table. Como no caso de LEFT JOIN, correspondências vazias são substituídas por valores denull.
A representação gráfica desse tipo de junção é um reflexo de espelho daquele que ilustramos para a junção à esquerda:
Vamos implementar o RIGHT JOIN em Java:
List articleRightJoinAuthor() {
String query = "SELECT ARTICLE.TITLE, AUTHOR.LAST_NAME, AUTHOR.FIRST_NAME "
+ "FROM ARTICLE RIGHT JOIN AUTHOR ON AUTHOR.ID=ARTICLE.AUTHOR_ID";
return executeQuery(query);
}
Novamente, vamos dar uma olhada em nossos dados de teste. Como essa operação de junção recupera todos os registros da segunda tabela, esperamos recuperar cinco linhas e, como nem todos os autores já escreveram um artigo, esperamos alguns valoresnull na coluna TITLE:
@Test
public void whenQueryWithRightJoin_thenShouldReturnProperRows() {
List articleWithAuthorList = articleWithAuthorDAO.articleRightJoinAuthor();
assertThat(articleWithAuthorList).hasSize(5);
assertThat(articleWithAuthorList).anyMatch(row -> row.getTitle() == null);
}
7. Junção externa completa
Essa operação de junção é provavelmente a mais complicada. The FULL JOIN selects all rows from both the first and the second table regardless of whether the condition is met or not.
Também podemos representar a mesma ideia que todos os valores de cada um dos conjuntos que se cruzam:
Vamos dar uma olhada na implementação do Java:
List articleOuterJoinAuthor() {
String query = "SELECT ARTICLE.TITLE, AUTHOR.LAST_NAME, AUTHOR.FIRST_NAME "
+ "FROM ARTICLE FULL JOIN AUTHOR ON AUTHOR.ID=ARTICLE.AUTHOR_ID";
return executeQuery(query);
}
Agora, podemos testar nosso método:
@Test
public void whenQueryWithFullJoin_thenShouldReturnProperRows() {
List articleWithAuthorList = articleWithAuthorDAO.articleOuterJoinAuthor();
assertThat(articleWithAuthorList).hasSize(6);
assertThat(articleWithAuthorList).anyMatch(row -> row.getTitle() == null);
assertThat(articleWithAuthorList).anyMatch(row -> row.getAuthorFirstName() == null);
}
Mais uma vez, vamos dar uma olhada nos dados de teste. Temos cinco artigos diferentes, um dos quais não tem autor e quatro autores, um dos quais não possui um artigo atribuído. Como resultado da junção completa, esperamos recuperar seis linhas. Quatro deles são comparados entre si, e os dois restantes não são. Por esse motivo, também assumimos que haverá pelo menos uma linha com valoresnull em ambas as colunas de dados AUTHOR e uma com um valornull na coluna TITLE.
8. Conclusão
Neste artigo, exploramos os tipos básicos de junções SQL. Vimos exemplos de quatro tipos de junções e como elas podem ser implementadas em Java.
Como sempre, o código completo usado neste artigo está disponívelover on GitHub.