结合表使用连接

如何使用连接将两个表中的信息组合在一起。

您在一个表中拥有数据,您想将其与另一个表中的数据结合起来。要做到这一点,您需要使用JOIN来告诉数据库一个表中的行如何与另一个表中的行相关联。(注意,连接仅适用于关系数据库。)

有关连接的背景知识,请查看Metabase中的连接,该页面通过查询构建器逐步介绍如何创建连接——无需编写代码。请注意,“连接”这个词有点误导,因为您并不是在连接表。您正在从两个(或更多)不同的表中提取行,并返回一个包含两个表中列的行集。

本文展示了如何使用SQL创建连接,您可以在Metabase中通过点击+ 新建,选择SQL查询,然后选择原始数据 > 示例数据库来跟随操作。

使用示例数据库的示例连接

假设我们想问一个问题,返回包含多个表的列的行。例如,我们想知道订单日期、ID和总计,同时还要包含一些关于订购产品的信息。这是我们想要生成的表

| Order date | Order ID | Product Title       | Product Category | Product Rating | Order total |
|------------|----------|---------------------|------------------|----------------|-------------|
| 12/25/2016 | 448      | Rustic Paper Wallet | Gizmo            | 4.6            | 30.86326113 |
| ...        | ...      | ...                 | ...              | ...            | ...         |

现在让我们看一下示例数据库中的表。我们会发现我们可以从两个表中获取必要的列,一个是orders

| ID  | Person | Product ID | Subtotal    | Tax  | Total       | Discount | Created At | Quantity | Person       | Product ID             |
|-----|--------|------------|-------------|------|-------------|----------|------------|----------|--------------|------------------------|
| 1   | 1      | 14         | 37.64814539 | 2.07 | 39.71814539 |          | 2/11/2019  | 2        | Hudson Borer | Awesome Concrete Shoes |
| ... | ...    | ...        | ...         | ...  | ...         | ...      | ...        | ...      | ...          | ...                    |

Products

| ID  | Ean           | Title               | Category | Vendor                       | Price       | Rating | Created At |
|-----|---------------|---------------------|----------|------------------------------|-------------|--------|------------|
| 1   | 1018947080336 | Rustic Paper Wallet | Gizmo    | Swaniawski, Casper and Hilll | 29.46326113 | 4.6    | 7/19/2017  |
| ... | ...           | ...                 | ...      | ...                          | ...         | ...    | ...        |

请注意,在SQL编辑器中,您可以通过点击书籍图标来打开数据参考侧边栏,查看数据库中表和字段的信息。学习如何连接表的大部分工作就是了解您正在处理的表以及它们之间的关系。

The Data Reference sidebar in the SQL editor shows info about tables and fields in your database.

我们需要从这两个表中获取列以生成我们的目标结果,我们将使用连接来完成。

查询

这里是答案

SELECT
  o.created_at AS "order date",
  o.id AS "order id",
  p.title AS "product title",
  p.category AS "product category",
  p.rating AS "product rating",
  o.total AS "order total"
FROM
-- joining orders to products
  orders AS o
  JOIN products AS p ON o.product_id = p.id

注意,注释(以--开头的行)不是必需的。它只是为了指出代码的关键部分:连接语句。

此查询告诉数据库,可以通过将Orders表中的外键“对齐”到Products表的实体键(也称为主键)来将Orders表中的行与Products表中的行组合。我们还为每个表分配了别名(orders AS oproducts AS p),因此您可以在SELECT语句中知道每个字段来自哪个表(o.created_at来自orders表,等等)。

实体键是指包含每行唯一标识符的表中的列。在Orders表中,每行是一个订单,有一个ID。同样,在Products表中也是这样:每行是一个产品,有一个ID。外键是指引用另一表中的实体键的列。在这种情况下,Orders表包含一个product_id,它引用Products表中的特定行。如果我们检查数据参考侧边栏中的Products表,我们会看到它有两个连接,一个连接到Orders表,另一个连接到reviews表。

The Products table has connections to the Orders and Reviews tables.

我们可以通过将Products表中的每一行与Orders表中的每一行组合,然后使用这样的WHERE子句进行筛选来得到相同的结果

SELECT
  o.created_at as "order date",
  o.id as "order id",
  p.title as "product title",
  p.category as "product category",
  p.rating as "product rating",
  o.total as "order total"

-- a join using a WHERE clause to "line up" the entity and foreign keys
FROM
  orders AS o,
  products AS p
WHERE
  o.product_id = p.id

此查询表示:将《Orders》表中的每一行与《Products》表中的每一行组合,然后过滤结果,只包含《Orders》表中的《product_id》与《Products》表中的《id》匹配的行。我们建议您使用第一种形式(带《ON》),以避免混淆:《ON始终引入连接条件,而《WHERE》用于所有类型的过滤。有关更多信息,请参阅编写SQL查询的最佳实践

让我们聚焦于那个连接语句

FROM
-- joining orders to products
  orders AS o
  JOIN products AS p ON o.product_id = p.id

当您连接表时,您使用《ON》关键字来指定表A(《Orders》)中的哪个字段对应于表B(《Products》),以便数据库了解如何“对齐”数据。《o.product_id = p.id》是一个表达式,其结果为真或假;数据库将只返回表达式为真的行。

连接多个表

您可以通过列出每个连接类型及其条件来链式连接多个连接。以下是一个包含《people》表字段的连接示例

SELECT
  *
FROM
  orders AS o
  JOIN products AS p ON o.product_id = p.id
  JOIN people AS u ON o.user_id = u.id

在这里,您需要进行大量的水平滚动,因为此查询将产生许多列(实际上是每个表的所有列)。您还将看到行与行之间重复的信息。例如,结果将重复每个客户订单的地址。

| ID  | USER_ID | PRODUCT_ID | SUBTOTAL    | TAX | TOTAL       | DISCOUNT | CREATED_AT       | QUANTITY | ID  | EAN           | TITLE               | CATEGORY | VENDOR                       | PRICE       | RATING | CREATED_AT      | ID  | ADDRESS                 | EMAIL                  | PASSWORD                             | NAME         | CITY    | LONGITUDE  | STATE | SOURCE | BIRTH_DATE | ZIP   | LATITUDE | CREATED_AT    |
|-----|---------|------------|-------------|-----|-------------|----------|------------------|----------|-----|---------------|---------------------|----------|------------------------------|-------------|--------|-----------------|-----|-------------------------|------------------------|--------------------------------------|--------------|---------|------------|-------|--------|------------|-------|----------|---------------|
| 448 | 61      | 1          | 29.46326113 | 1.4 | 30.86326113 |          | 12/25/2016 22:19 | 2        | 1   | 1018947080336 | Rustic Paper Wallet | Gizmo    | Swaniawski, Casper and Hilll | 29.46326113 | 4.6    | 7/19/2017 19:44 | 61  | 7100 Hudson Chapel Road | [email protected] | 2da78e08-2bf7-41b8-a737-1acd815fb99c | Lina Labadie | Catawba | -81.017265 | NC    | Google | 3/28/1984  | 28609 | 35.69917 | 6/5/2016 4:03 |
| ... | ...     | ...        | ...         | ... | ...         | ...      | ...              | ...      | ... | ...           | ...                 | ...      | ...                          | ...         | ...    | ...             | ... | ...                     | ...                    | ...                                  | ...          | ...     | ...        | ...   | ...    | ...        | ...   | ...      | ...           |

在上面的查询中,您可以通过在《SELECT》语句中将《*》替换为仅指定您需要的列来减少返回的列数。

基于多个条件的连接

您可以在《ON》语句中包含多个真/假表达式来限制结果。这些真/假表达式被称为《strong>predicates》。我们已经在上述表中使用了predicates来连接表,例如《o.user_id = u.id》。

假设我们想知道

  • 订单的平均价格,
  • 按产品类别,
  • 以全价售出的。

我们将需要使用《Orders》表中的数据来计算单价,并从《Products》表获取产品类别和标价。以下是我们的查询

SELECT
  p.category AS "Product category",
  AVG(p.price) AS "Average price",
  COUNT(*) AS "Count of full price orders"
FROM
  orders AS o
  -- first predicate
  JOIN products AS p ON o.product_id = p.id
  -- second predicate: calculate the unit price
  -- and see if it corresponds to the product's listed price.
  AND o.subtotal / o.quantity = p.price
WHERE
  -- guard against divide-by-zero scenarios
  o.quantity > 0
GROUP BY
  p.category
ORDER BY
  COUNT(*) DESC

这将给我们

|Product category|Average price|Count of full price orders|
|----------------|-------------|--------------------------|
|Widget          |54.96699655  |168                       |
|Gizmo           |51.49700878  |137                       |
|Gadget          |54.87034242  |136                       |
|Doohickey       |51.69197973  |123                       |

不同类型的连接

本文中一直在使用的特定连接类型称为《内连接》。您还有其他连接表的方法;请参阅我们的SQL连接类型文章。

接下来:SQL连接类型

了解使用不同SQL连接类型所需了解的一切。

下一篇文章