关系数据库入门:表、主键外键与 SQL Join 实战(Data-Science-For-Beginners 第 5 课)
2026/9/13 10:36:46 网站建设 项目流程

关系数据库入门:表、主键外键与 SQL Join 实战(Data-Science-For-Beginners 第 5 课)

【免费下载链接】Data-Science-For-Beginners10 Weeks, 20 Lessons, Data Science for All!项目地址: https://gitcode.com/GitHub_Trending/da/Data-Science-For-Beginners

本篇指南以 Data-Science-For-Beginners 课程「Working with Data: Relational Databases」为核心,系统讲解关系数据库的核心概念——表、行与列、主键(PK)与外键(FK),并手把手演示如何使用 SQL 的SELECTWHEREINNER JOIN从多张表中检索和合并数据。读完本文,你将能够理解"为什么单表会带来数据冗余",掌握用主外键拆表建模的思路,并能在 VS Code 中直接操作仓库自带、真实可查询的 airports.db 完成实战练习。

一切从表开始:关系数据库的最小单元

关系数据库(Relational Database)最核心的组成单元是表(Table)。如果你用过 Excel 等电子表格软件,就已经熟悉它的基本形态:一张表由若干行(Row)列(Column)构成。行中存放的是我们真正关心的数据(如一座城市的名字、某年的降水量),而列则描述这些数据的含义——这类"描述数据的数据"有时也被称为元数据(Metadata)

以课程文档中的示例为例,我们用一张表存放城市信息:

CityCountry
TokyoJapan
AtlantaUnited States
AucklandNew Zealand

这里的列名CityCountry就是元数据,它说明每一行里存储的分别是什么;而每一行则是对一座城市的完整描述。关系数据库正是建立在这个"列 + 行"的朴素原理之上,它的强大之处在于:允许把信息分散到多张表中,从而支持更复杂的数据、避免重复,并让数据探索方式更加灵活。

单表方案的短板:冗余与僵化

当我们只有三座城市时,单表看起来无可挑剔。可一旦数据量增长,问题就会暴露出来。课程文档以"年度降水量"为例展开了两个典型反例。

反例一:纵向重复行。如果我们在同一张表里按年份追加降水数据,为了保存 Tokio 三年(2018、2019、2020)的数据,城市名和国家名就必须重复出现三遍:

CityCountryYearAmount
TokyoJapan20201690
TokyoJapan20191874
TokyoJapan20181445

这种重复既浪费存储空间,也会带来更新隐患——比如城市名拼写变更时,你必须同步修改所有重复行,否则数据就会不一致。

反例二:横向扩展列。换个思路,把年份变成列:

CityCountry201820192020
TokyoJapan144518741690
AtlantaUnited States177911111683
AucklandNew Zealand13869421176

虽然避免了行的重复,却引入了新的麻烦:每新增一个年份,就必须修改表结构添加新列;而且当数据量变大后,把年份作为列会让"检索某一年、按年份求和"这类计算变得非常别扭。

两个反例指向同一个结论:我们需要多张表,以及表与表之间的"关系(Relationship)"。通过把数据拆分,才能避免重复并获得操作上的灵活性。

关系的两个支点:主键与外键

拆分数据之前,必须先解决一个关键问题:如何唯一标识一张表中的一行?答案是主键(Primary Key,缩写PK)。

✅ 课程文档特别提醒:本课中"id"与"primary key"两个词会交替使用;这一概念同样适用于你稍后接触的 DataFrame——DataFrame 虽然不用"主键"这个术语,但行为上高度相似。

主键是用于标识表中某一行唯一记录的值。虽然理论上可以拿业务值当主键(例如直接用城市名),但实践中主键几乎总是数字或其它与业务无关的标识符——因为我们不希望主键发生改变,一旦主键变化,所有依赖它的关系都会被破坏。大多数数据库会自动生成自增主键。

于是课程把城市信息整理成一张带city_id主键的cities表:

city_idCityCountry
1TokyoJapan
2AtlantaUnited States
3AucklandNew Zealand

存放降水量的rainfall表则不再重复城市名与国家,而是只保存城市的引用:

rainfall_idcity_idYearAmount
1120181445
2120191874
3120201690
4220181779
5220191111
6220201683
7320181386
832019942
9320201176

注意rainfall表中的city_id列:它存放的值指向cities表的主键。在关系数据库术语中,这种"引用另一张表主键"的列被称为外键(Foreign Key,缩写 FK)。你可以把它理解成一个引用或指针——例如city_id为 1 的记录,就指向城市 Tokio。同时,新表也保留了自己的主键rainfall_id,因为每一张表都应该有自己的主键

[!NOTE] 外键常被缩写为FK;主键常被缩写为PK

用 SQL 检索数据:SELECT 与 WHERE

数据拆到两张表后,如何把需要的信息取回来?答案是 SQL——结构化查询语言(Structured Query Language)。像 MySQL、SQL Server、Oracle 这类关系数据库都支持 SQL,它是检索和修改关系数据库中数据的标准语言(SQL 常被读作 "sequel")。

检索数据的基础命令是SELECT:在SELECT后列出你想看的,在FROM后列出这些列所在的。如果只想显示所有城市名称:

SELECT city FROM cities; -- Output: -- Tokyo -- Atlanta -- Auckland

[!NOTE] SQL 语法不区分大小写,selectSELECT等价;但列名、表名在某些数据库上可能区分大小写。因此编程中最好的习惯是"默认一切都区分大小写"。写 SQL 时,业内惯例是把关键字统一写成大写。

上面的查询会返回所有城市。如果我们只关心新西兰的城市呢?这时需要过滤条件,SQL 的关键字是WHERE——即"在条件为真的地方":

SELECT city FROM cities WHERE country = 'New Zealand'; -- Output: -- Auckland

WHERE接受一个布尔表达式,只有满足条件的行才会出现在结果中。

合并两张表:INNER JOIN

到目前为止我们都是从单表取数。现在要把citiesrainfall的数据合并展示,这就要用到Join(连接)。Join 的本质是在两张表之间"缝合"起来:把一张表的某列值与另一张表某列的值一一匹配。

本课的例子是用rainfall表的city_idcities表的city_id做匹配,从而把每条降水记录关联回它的城市。这里使用的连接类型是INNER JOIN(内连接):如果某行在另一张表中找不到匹配,则不会出现在结果里。本例中每座城市都有降水数据,所以全部行都会显示。

先看第一步——仅指定连接列,把两表缝合:

SELECT cities.city, rainfall.amount FROM cities INNER JOIN rainfall ON cities.city_id = rainfall.city_id

说明:SELECT后以"表名.列名"的方式限定列,避免两张表出现同名列时产生歧义;ON后面给出两表连接的"缝线"条件。原文档示例中两列之间需以逗号分隔,这是 SQL 的合法写法。

在此基础上追加WHERE,只保留 2019 年的降水数据:

SELECT cities.city, rainfall.amount FROM cities INNER JOIN rainfall ON cities.city_id = rainfall.city_id WHERE rainfall.year = 2019 -- Output -- city | amount -- -------- | ------ -- Tokyo | 1874 -- Atlanta | 1111 -- Auckland | 942

可以看到,INNER JOIN负责"横向拼表",WHERE负责"纵向筛行",两者组合即可完成"按城市关联、按年份过滤"的典型分析需求。这正是关系数据库的威力所在:数据虽然分散在多张表,但通过连接可以随时按需重组,进行展示、计算与各种灵活操作。

实战:用 airports.db 练习连接查询

理论讲完,现在进入可运行的实战环节。课程为这一课配套了真实数据库 airports.db(基于 SQLite 构建),其中包含英国与爱尔兰的机场数据。完整练习说明见 作业文档,下面给出从零开始的操作路径与参考答案。

准备环境:VS Code + SQLite 扩展

  1. 安装 Visual Studio Code;
  2. 在其扩展市场安装SQLite 扩展SQLiteby alexcvzz),该扩展提供了图形化的数据库浏览与查询入口。

[!NOTE] 更详细的扩展使用说明请查阅扩展自身的文档页面。

打开数据库并建立查询窗口

  1. 打开 Visual Studio Code;
  2. Ctrl-Shift-P(Mac 上为Cmd-Shift-P),输入并执行SQLite: Open database
  3. 选择Choose database from file,打开上文提到的 airports.db;
  4. 打开数据库后界面可能没有明显变化,此时再按Ctrl-Shift-P(Mac 上为Cmd-Shift-P),输入并执行SQLite: New query新建查询窗口;
  5. 在查询窗口中编写 SQL,用Ctrl-Shift-Q(Mac 上为Cmd-Shift-Q)执行。

[!NOTE] 数据库的 schema(结构设计)决定了你能查询什么。airports库包含两张表:Cities存放英国与爱尔兰的城市列表,Airports存放机场列表。由于一座城市可能拥有多个机场,所以拆成两张表,通过外键关联——这正是本文前半部分讲解的建模思路。

数据库结构速览

对照数据库实际定义(可通过PRAGMA table_info查看),两张表的结构如下:

Cities
id (PK, integer)
city (text)
country (text)
Airports
id (PK, integer)
name (text)
code (text)
city_id (FK → Cities.id)

从源码结构看(airports.db 实际表定义),Airports.city_id即为指向Cities.id的外键;Airports表中目前存有 181 条机场记录。

四道练习与参考答案

以下查询结果均基于仓库中的 airports.db 实际执行验证,可直接在 VS Code 查询窗口复现。

练习 1:列出Cities表中的全部城市名

SELECT city FROM Cities;

练习 2:列出Cities表中位于爱尔兰(Ireland)的全部城市

SELECT city FROM Cities WHERE country = 'Ireland';

执行后共返回 16 行,包括 Cork、Galway、Dublin、Shannon、Kerry 等爱尔兰城市。

练习 3:列出所有机场的名称及其所在城市和国家

SELECT a.name, c.city, c.country FROM Airports a INNER JOIN Cities c ON a.city_id = c.id;

这里用ac作为表的别名(Alias),并以外键a.city_id = c.id作为连接条件。结果中的部分行例如:

Belfast International Airport | Belfast | United Kingdom George Best Belfast City Airport | Belfast | United Kingdom Birmingham International Airport | Birmingham | United Kingdom

练习 4:找出伦敦(London, United Kingdom)的全部机场

SELECT a.name, a.code FROM Airports a INNER JOIN Cities c ON a.city_id = c.id WHERE c.city = 'London' AND c.country = 'United Kingdom';

这一步同时用到了本文的全部知识点:INNER JOIN关联两张表,WHERE过滤目标城市,最终返回伦敦的 6 个机场及其 ICAO 代码:

namecode
London Luton AirportEGGW
London Gatwick AirportEGKK
London City AirportEGLC
London Heathrow AirportEGLL
London Stansted AirportEGSS
London HeliportEGLW

可以看到,练习 4 与前文"cities + rainfall 按 2019 年过滤"的 JOIN 思路完全一致:先连接、后过滤。掌握了SELECTFROMWHEREINNER JOINON这五个要素,你就已经掌握了关系数据库最核心的日常操作。

总结

关系数据库的核心思想,是把信息拆分到多张表中,再在需要展示与分析时通过 Join重组起来。这种"拆-合"的循环带来了极高的灵活性,使计算和数据操作不再受限于单表结构。通过本课,你已经理解了:

  • 表、行、列与元数据的关系;
  • 单表方案的两种典型缺陷(行重复、列扩展导致的冗余与僵化);
  • 用主键(PK)唯一标识行、用外键(FK)跨表引用,从而建立表间关系;
  • SELECT ... FROM检索、用WHERE过滤、用INNER JOIN ... ON合并两张表;
  • 在真实数据库 airports.db 上完成从环境搭建到四道连接查询练习的完整流程。

下一步,你可以进入 第 6 课:非关系型数据,对比 NoSQL 与关系模型的差异;也可以在仓库中继续学习 用 Python 处理数据,把 SQL 思维迁移到 DataFrame 中。本文对应的英文原版文档见 05-relational-databases 英文 README,德语译本即 当前文档。

【免费下载链接】Data-Science-For-Beginners10 Weeks, 20 Lessons, Data Science for All!项目地址: https://gitcode.com/GitHub_Trending/da/Data-Science-For-Beginners

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询