1. 关联查询
关系型数据库把不同类型的数据放在不同表中。任务只保存 project_id,项目只保存 owner_id,不会在每条任务中重复项目名称和用户邮箱。
当接口需要同时返回任务、项目和负责人信息时,就要根据这些字段之间的关系重新组合表中的行。SQL 使用 JOIN 表达这种关联。
JOIN 本身并不要求数据库已经定义外键,它只按照连接条件判断两行是否匹配。外键的职责是保证 tasks.project_id、projects.owner_id 等引用值确实存在,从而让这些连接关系长期保持可靠。
现有关系如下:
1projects.owner_id -> users.id2tasks.project_id -> projects.id
常见连接类型如下:
| 类型 | 返回哪些行 |
|---|---|
INNER JOIN | 只返回左右两侧能够匹配的行 |
LEFT JOIN | 返回左侧全部行,右侧没有匹配时用 NULL 补齐 |
RIGHT JOIN | 返回右侧全部行,左侧没有匹配时用 NULL 补齐 |
FULL JOIN | 返回两侧全部行,任意一侧没有匹配时用 NULL 补齐 |
CROSS JOIN | 返回左右两侧行的所有组合,也就是笛卡尔积 |
实际业务最常使用 INNER JOIN 和 LEFT JOIN,本文也会重点讲解这两种连接,最后再说明 CROSS JOIN 的用途和风险。
读取任务及其项目名称:
1SELECT2tasks.id,3tasks.title,4projects.name AS project_name5FROM tasks6JOIN projects ON projects.id = tasks.project_id;
ON 后面的条件说明两张表的行如何匹配。对于每条任务,数据库找到 projects.id 与 tasks.project_id 相等的项目,再把需要的列放进同一结果行。
这里的 JOIN 默认等同于 INNER JOIN,只返回两边都能匹配的行。由于任务外键要求项目存在,正常数据都能找到对应项目。连接只会产生本次查询使用的结果集,不会把三张表合并存储,也不会修改原表。
2. 表与列别名
多表查询中经常出现同名列,因此应该给表设置简短且能辨认的别名:
01SELECT02t.id AS task_id,03t.title AS task_title,04t.status,05p.id AS project_id,06p.name AS project_name,07u.id AS owner_id,08u.display_name AS owner_name09FROM tasks AS t10JOIN projects AS p ON p.id = t.project_id11JOIN users AS u ON u.id = p.owner_id12WHERE t.archived_at IS NULL13ORDER BY t.created_at DESC, t.id DESC;
别名只在当前 SQL 中有效,不会修改真实表名。为表指定别名后,同一层查询应该通过别名引用它,例如写了 FROM tasks AS t 后就继续使用 t.id,而不是 tasks.id。t、p、u 适合短查询;查询很复杂时,也可以使用 task、project、owner 等更有意义的别名。
限定列名前缀不仅用于解决歧义,也能说明数据来源。看到 u.display_name,读者不必回到表结构中猜测名称来自哪张表。