1. 简介
SQL 中的 JOIN 是数据库查询中最核心的操作之一,它允许我们从多个表中提取相关数据。本文将介绍几种常见的 SQL JOIN 类型,并通过 Java 示例展示如何在实际项目中使用它们。
2. 定义数据模型
我们先创建两个简单的表:AUTHOR
和 ARTICLE
:
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)
);
插入测试数据:
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);
定义 Java 对象用于存储查询结果:
class ArticleWithAuthor {
private String title;
private String authorFirstName;
private String authorLastName;
// 标准构造方法、getter 和 setter
}
3. 环境配置
我们使用 PostgreSQL 数据库,端口为 5432。部分 JOIN 操作(如 FULL JOIN)在 MySQL 或 H2 中不支持,但在 PostgreSQL 中可以正常使用。
Maven 依赖:
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.5.4</version>
<scope>test</scope>
</dependency>
建立数据库连接:
Class.forName("org.postgresql.Driver");
Connection connection = DriverManager.
getConnection("jdbc:postgresql://localhost:5432/myDb", "user", "pass");
定义 DAO 类和辅助方法:
class ArticleWithAuthorDAO {
private final Connection connection;
// 构造方法
private List<ArticleWithAuthor> 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<ArticleWithAuthor> mapToList(ResultSet resultSet) throws SQLException {
List<ArticleWithAuthor> 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;
}
}
4. INNER JOIN(内连接)
✅ 定义:只返回两个表中满足连接条件的行。
SELECT ARTICLE.TITLE, AUTHOR.LAST_NAME, AUTHOR.FIRST_NAME
FROM ARTICLE INNER JOIN AUTHOR
ON AUTHOR.ID=ARTICLE.AUTHOR_ID
实现方法:
List<ArticleWithAuthor> 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);
}
测试:
@Test
public void whenQueryWithInnerJoin_thenShouldReturnProperRows() {
List<ArticleWithAuthor> articleWithAuthorList = articleWithAuthorDAO.articleInnerJoinAuthor();
assertThat(articleWithAuthorList).hasSize(4);
assertThat(articleWithAuthorList)
.noneMatch(row -> row.getAuthorFirstName() == null || row.getTitle() == null);
}
⚠️ 注意:INNER JOIN 不会返回没有匹配的行,比如没有作者的文章或没有文章的作者。
5. LEFT JOIN(左连接)
✅ 定义:返回左表所有行,右表无匹配时用 NULL 填充。
SELECT ARTICLE.TITLE, AUTHOR.LAST_NAME, AUTHOR.FIRST_NAME
FROM ARTICLE LEFT JOIN AUTHOR
ON AUTHOR.ID=ARTICLE.AUTHOR_ID
实现方法:
List<ArticleWithAuthor> 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);
}
测试:
@Test
public void whenQueryWithLeftJoin_thenShouldReturnProperRows() {
List<ArticleWithAuthor> articleWithAuthorList = articleWithAuthorDAO.articleLeftJoinAuthor();
assertThat(articleWithAuthorList).hasSize(5);
assertThat(articleWithAuthorList).anyMatch(row -> row.getAuthorFirstName() == null);
}
✅ 用途:适合查询所有文章并附带作者信息,即使某些文章没有作者。
6. RIGHT JOIN(右连接)
✅ 定义:返回右表所有行,左表无匹配时用 NULL 填充。
SELECT ARTICLE.TITLE, AUTHOR.LAST_NAME, AUTHOR.FIRST_NAME
FROM ARTICLE RIGHT JOIN AUTHOR
ON AUTHOR.ID=ARTICLE.AUTHOR_ID
实现方法:
List<ArticleWithAuthor> 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);
}
测试:
@Test
public void whenQueryWithRightJoin_thenShouldReturnProperRows() {
List<ArticleWithAuthor> articleWithAuthorList = articleWithAuthorDAO.articleRightJoinAuthor();
assertThat(articleWithAuthorList).hasSize(5);
assertThat(articleWithAuthorList).anyMatch(row -> row.getTitle() == null);
}
⚠️ 注意:LEFT JOIN 与 RIGHT JOIN 逻辑相反,实际开发中 LEFT JOIN 使用更广泛。
7. FULL JOIN(全连接)
✅ 定义:返回两个表中所有行,不匹配时用 NULL 填充。
SELECT ARTICLE.TITLE, AUTHOR.LAST_NAME, AUTHOR.FIRST_NAME
FROM ARTICLE FULL JOIN AUTHOR
ON AUTHOR.ID=ARTICLE.AUTHOR_ID
实现方法:
List<ArticleWithAuthor> 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);
}
测试:
@Test
public void whenQueryWithFullJoin_thenShouldReturnProperRows() {
List<ArticleWithAuthor> articleWithAuthorList = articleWithAuthorDAO.articleOuterJoinAuthor();
assertThat(articleWithAuthorList).hasSize(6);
assertThat(articleWithAuthorList).anyMatch(row -> row.getTitle() == null);
assertThat(articleWithAuthorList).anyMatch(row -> row.getAuthorFirstName() == null);
}
⚠️ 注意:FULL JOIN 不是所有数据库都支持(如 MySQL),建议使用 PostgreSQL 或 Oracle。
8. 总结
本文介绍了 SQL 中常见的四种 JOIN 类型:
类型 | 是否包含左表全部 | 是否包含右表全部 | 空值填充 |
---|---|---|---|
INNER JOIN | ❌ | ❌ | ❌ |
LEFT JOIN | ✅ | ❌ | ✅ |
RIGHT JOIN | ❌ | ✅ | ✅ |
FULL JOIN | ✅ | ✅ | ✅ |
这些连接操作在实际开发中非常常用,特别是在处理多表关联查询时。掌握它们的使用方式和适用场景,能极大提升 SQL 查询的效率和准确性。
完整代码可在 GitHub 上查看。