1. 简介

SQL 中的 JOIN 是数据库查询中最核心的操作之一,它允许我们从多个表中提取相关数据。本文将介绍几种常见的 SQL JOIN 类型,并通过 Java 示例展示如何在实际项目中使用它们。

2. 定义数据模型

我们先创建两个简单的表:AUTHORARTICLE

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

inner join

实现方法:

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

left join

实现方法:

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

right join

实现方法:

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

full join

实现方法:

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 上查看。


原始标题:Types of SQL Joins