📣

TiDB Cloud Serverless 现已更名为
Starter
!此页面由 AI 自动翻译,英文原文请见
此处。

Multi-table Join Queries

在许多场景中,你需要用一条查询语句从多个表中获取数据。你可以使用 JOIN 语句将两个或多个表中的数据结合起来。

Join types

本节详细介绍各种连接类型。

INNER JOIN

内连接的结果只返回满足连接条件的行。

Inner Join

例如,如果你想知道最 prolific 的作者,你需要将名为 authors 的作者表与名为 book_authors 的图书作者表进行连接。

    在以下 SQL 语句中,使用关键字 JOIN 来声明你希望将左表 authors 和右表 book_authors 作为内连接连接,连接条件为 a.id = ba.author_id。结果集只包含满足连接条件的行。如果某个作者没有写过任何书,那么他在 authors 表中的记录将不满足连接条件,因此不会出现在结果集中。

    SELECT ANY_VALUE(a.id) AS author_id, ANY_VALUE(a.name) AS author_name, COUNT(ba.book_id) AS books FROM authors a JOIN book_authors ba ON a.id = ba.author_id GROUP BY ba.author_id ORDER BY books DESC LIMIT 10;

    查询结果如下:

    +------------+----------------+-------+ | author_id | author_name | books | +------------+----------------+-------+ | 431192671 | Emilie Cassin | 7 | | 865305676 | Nola Howell | 7 | | 572207928 | Lamar Koch | 6 | | 3894029860 | Elijah Howe | 6 | | 1150614082 | Cristal Stehr | 6 | | 4158341032 | Roslyn Rippin | 6 | | 2430691560 | Francisca Hahn | 6 | | 3346415350 | Leta Weimann | 6 | | 1395124973 | Albin Cole | 6 | | 2768150724 | Caleb Wyman | 6 | +------------+----------------+-------+ 10 rows in set (0.01 sec)
    public List<Author> getTop10AuthorsOrderByBooks() throws SQLException { List<Author> authors = new ArrayList<>(); try (Connection conn = ds.getConnection()) { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(""" SELECT ANY_VALUE(a.id) AS author_id, ANY_VALUE(a.name) AS author_name, COUNT(ba.book_id) AS books FROM authors a JOIN book_authors ba ON a.id = ba.author_id GROUP BY ba.author_id ORDER BY books DESC LIMIT 10; """); while (rs.next()) { Author author = new Author(); author.setId(rs.getLong("author_id")); author.setName(rs.getString("author_name")); author.setBooks(rs.getInt("books")); authors.add(author); } } return authors; }

    LEFT OUTER JOIN

    左外连接返回左表中的所有行,以及满足连接条件的右表中的值。如果右表中没有匹配的行,则用 NULL 填充。

    Left Outer Join

    在某些情况下,你希望用多张表完成数据查询,但又不希望因为连接条件不满足而导致数据集变得过小。

    例如,在 Bookshop 应用的首页,你想展示一份带有平均评分的新书列表。在这种情况下,新书可能还没有被任何人评分。使用内连接会导致这些未评分的书籍信息被过滤掉,这不是你想要的。

      在以下 SQL 语句中,使用 LEFT JOIN 关键字声明左表 books 将与右表 ratings 进行左外连接,从而确保返回 books 表中的所有行。

      SELECT b.id AS book_id, ANY_VALUE(b.title) AS book_title, AVG(r.score) AS average_score FROM books b LEFT JOIN ratings r ON b.id = r.book_id GROUP BY b.id ORDER BY b.published_at DESC LIMIT 10;

      查询结果如下:

      +------------+---------------------------------+---------------+ | book_id | book_title | average_score | +------------+---------------------------------+---------------+ | 3438991610 | The Documentary of lion | 2.7619 | | 3897175886 | Torey Kuhn | 3.0000 | | 1256171496 | Elmo Vandervort | 2.5500 | | 1036915727 | The Story of Munchkin | 2.0000 | | 270254583 | Tate Kovacek | 2.5000 | | 1280950719 | Carson Damore | 3.2105 | | 1098041838 | The Documentary of grasshopper | 2.8462 | | 1476566306 | The Adventures of Vince Sanford | 2.3529 | | 4036300890 | The Documentary of turtle | 2.4545 | | 1299849448 | Antwan Olson | 3.0000 | +------------+---------------------------------+---------------+ 10 rows in set (0.30 sec)

      似乎最新出版的书已经有很多评分了。为了验证上述方法,让我们通过以下 SQL 语句删除 The Documentary of lion 这本书的所有评分:

      DELETE FROM ratings WHERE book_id = 3438991610;

      再次查询。 The Documentary of lion 这本书仍然出现在结果集中,但由右表 ratingsscore 计算得出的 average_score 列变成了 NULL

      +------------+---------------------------------+---------------+ | book_id | book_title | average_score | +------------+---------------------------------+---------------+ | 3438991610 | The Documentary of lion | NULL | | 3897175886 | Torey Kuhn | 3.0000 | | 1256171496 | Elmo Vandervort | 2.5500 | | 1036915727 | The Story of Munchkin | 2.0000 | | 270254583 | Tate Kovacek | 2.5000 | | 1280950719 | Carson Damore | 3.2105 | | 1098041838 | The Documentary of grasshopper | 2.8462 | | 1476566306 | The Adventures of Vince Sanford | 2.3529 | | 4036300890 | The Documentary of turtle | 2.4545 | | 1299849448 | Antwan Olson | 3.0000 | +------------+---------------------------------+---------------+

      如果你用 INNER JOIN 会发生什么?可以自己试试。

      public List<Book> getLatestBooksWithAverageScore() throws SQLException { List<Book> books = new ArrayList<>(); try (Connection conn = ds.getConnection()) { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(""" SELECT b.id AS book_id, ANY_VALUE(b.title) AS book_title, AVG(r.score) AS average_score FROM books b LEFT JOIN ratings r ON b.id = r.book_id GROUP BY b.id ORDER BY b.published_at DESC LIMIT 10; """); while (rs.next()) { Book book = new Book(); book.setId(rs.getLong("book_id")); book.setTitle(rs.getString("book_title")); book.setAverageScore(rs.getFloat("average_score")); books.add(book); } } return books; }

      RIGHT OUTER JOIN

      右外连接返回右表中的所有记录,以及满足连接条件的左表中的值。如果没有匹配的值,则用 NULL 填充。

      Right Outer Join

      CROSS JOIN

      当连接条件为常数时,两个表之间的内连接称为 cross join。交叉连接会将左表的每一条记录与右表的所有记录进行连接。如果左表的记录数为 m,右表的记录数为 n,那么结果集中会生成 m * n 条记录。

      LEFT SEMI JOIN

      TiDB 不支持 LEFT SEMI JOIN table_name 作为 SQL 语法。但在执行计划层面,子查询相关的优化 会将 semi join 作为重写后等价 JOIN 查询的默认连接方式。

      Implicit join

      在 SQL 标准加入显式声明 JOIN 语句之前,可以在 SQL 语句中使用 FROM t1, t2 子句连接两个或多个表,并用 WHERE t1.id = t2.id 子句指定连接条件。可以理解为一种隐式连接,实际上是用内连接连接表。

      TiDB 支持以下通用的表连接算法。

      优化器会根据连接表中的数据量等因素选择合适的连接算法执行。你可以通过使用 EXPLAIN 语句查看查询使用了哪种连接算法。

      如果 TiDB 的优化器没有按照最优的连接算法执行,你可以使用 Optimizer Hints 强制 TiDB 使用更优的连接算法。

      例如,假设上面左连接查询的示例使用 Hash Join 算法会更快,但未被优化器选择,你可以在 SELECT 关键字后添加提示 /*+ HASH_JOIN(b, r) */。注意,如果表有别名,提示中也要使用别名。

      EXPLAIN SELECT /*+ HASH_JOIN(b, r) */ b.id AS book_id, ANY_VALUE(b.title) AS book_title, AVG(r.score) AS average_score FROM books b LEFT JOIN ratings r ON b.id = r.book_id GROUP BY b.id ORDER BY b.published_at DESC LIMIT 10;

      与连接算法相关的提示:

      Join orders

      在实际业务场景中,多表连接语句非常常见。连接的执行效率与各个表在连接中的顺序有关。TiDB 使用 Join Reorder 算法来确定多表连接的顺序。

      如果优化器选择的连接顺序不如预期的最优,你可以使用 STRAIGHT_JOIN 强制 TiDB 按照 FROM 子句中表的顺序进行连接。

      EXPLAIN SELECT * FROM authors a STRAIGHT_JOIN book_authors ba STRAIGHT_JOIN books b WHERE b.id = ba.book_id AND ba.author_id = a.id;

      关于此 Join Reorder 算法的实现细节和限制,详见 Introduction to Join Reorder Algorithm

      See also

      Need help?

      DiscordSlack 上向社区提问,或 提交支持工单

      文档内容是否有帮助?