如何在 SQL 中使用主键

0 股票
0
0
0
0

介绍

关系型数据库的一大优势在于其数据结构清晰、格式规范。这种结构是通过使用具有固定列的表、遵循明确定义的数据类型以及确保每行数据格式一致来实现的。将数据以行的形式存储在表中时,能够明确地查找和引用这些数据同样至关重要。在结构化查询语言 (SQL) 中,这可以通过主键来实现,主键充当关系型数据库表中每行数据的标识符。.

在本教程中,您将学习主键,并使用几种不同类型的主键来标识数据库表中的唯一行。您将使用一些示例数据集,创建单列主键、多列主键和自增顺序主键。.

先决条件

要遵循本指南,您需要一台运行基于 SQL 的关系数据库管理系统 (RDBMS) 的计算机。本指南中的说明和示例已在以下环境中验证:

  • 服务器运行Ubuntu 20.04,有一个具有管理员权限的非root用户,并且配置了UFW防火墙。
  • 服务器上已安装并配置了 MySQL 安全措施。
  • 执行 SELECT 查询从数据库中检索数据的基本介绍

注意:许多关系数据库管理系统 (RDBMS) 使用各自独特的 SQL 实现。虽然本教程中提到的命令适用于大多数 RDBMS,并且主键是 SQL 标准的一部分,但某些功能是数据库特有的,因此如果您在 MySQL 以外的系统上测试这些命令,则具体的语法或输出可能会有所不同。.

您还需要一个空数据库,用于创建带有主键的表。我们建议您参考下文“连接到 MySQL 并设置示例数据库”部分,了解如何连接到 MySQL 服务器以及创建本指南示例中使用的测试数据库。.

连接到 MySQL 并设置示例数据库

在本节中,您将连接到 MySQL 服务器并创建一个数据库实例,以便您可以使用本指南中的示例。.

如果您的 SQL 数据库系统运行在远程服务器上,请从本地计算机通过 SSH 连接到您的服务器:

ssh sammy@your_server_ip

然后打开 MySQL 服务器提示符,并将 Sami 替换为您的 MySQL 用户名:

mysql -u sammy -p

创建一个名为 primary_keys 的数据库:

CREATE DATABASE primary_keys;

如果数据库创建成功,您将收到以下输出:

Output
Query OK, 1 row affected (0.01 sec)

要选择 primary_keys 数据库,请运行以下 USE 语句:

USE primary_keys;

您将得到以下输出:

Output
Database changed

选择数据库后,您可以在其中创建示例表。现在,您可以继续阅读本指南的其余部分,开始在 MySQL 中使用主键。.

大调简介

关系数据库中的数据存储在具有特定、统一结构的表中,这些表由一行行组成。表定义描述了表中的列以及每列可以存储的数据类型。仅此就足以将信息存储在数据库中,并使用 WHERE 子句通过各种筛选条件查找信息。然而,这种结构并不能保证每一行都能被明确地找到。.

想象一下,有一个数据库记录了所有获准在公共道路上行驶的已注册车辆。该数据库包含车辆的品牌、型号、生产年份和车身颜色等信息。但是,如果您要查找一辆2007年生产的红色雪佛兰科迈罗,您可能会找到不止一辆。毕竟,汽车制造商会将同一款车出售给多个客户。这就是为什么每辆已注册车辆都有车牌来识别车辆的原因。如果您搜索车牌号为OFP857的车辆,您可以确定只会找到一辆。这是因为根据法律规定,车牌号是唯一标识已注册车辆的。在关系数据库中,这样的数据被称为主键。.

主键是数据库表中某一列或某一组列中的唯一标识符,用于明确标识表中的每一行。主键的技术特性体现在以下几个方面:

  • 主键必须使用唯一值。如果主键由多个列组成,则这些列中的值组合在整个表中必须是唯一的。由于主键旨在唯一标识每一行,因此它不能出现多次。.
  • 主键不应包含 NULL 值。.
  • 每个数据库表只能使用一个主键。.

数据库引擎会强制执行这些规则,因此如果在表中定义了主键,则可以相信这些属性是正确的。.

除了这些技术特性之外,您还应该考虑数据的内容,以确定哪种类型的数据适合作为主键。自然键是数据集中已存在的标识符,而代理键是人为创建的标识符。.

有些数据结构的主键自然存在于数据集中,例如车辆数据库中的车牌号或美国公民登记册中的社会安全号码。有时,这类标识符并非单个值,而是一对或多个值的组合。例如,在本地城市房屋名录中,仅凭街道名称或门牌号无法唯一标识一栋房屋。一条街道上可能有多栋房屋,同一个门牌号也可能出现在多条街道上。但街道名称和门牌号的组合可以作为唯一的房屋标识符。这类自然存在的标识符被称为自然键。.

然而,很多时候,仅凭单列或少数几列的值无法唯一标识数据。这时,就需要创建人工主键,例如使用数字序列或随机生成的标识符(如UUID)。这类键被称为代理键。.

在以下章节中,您将基于一个或多个列创建自然键,并在无法使用自然键的表中创建备用键。.

在列上创建主键

在许多情况下,数据集自然包含一个可用于唯一标识表中行的列。在这种情况下,您可以创建一个自然键来描述数据。继续前面的车辆登记数据库示例,假设有一个具有以下结构的表:

Sample table
+---------------+-----------+------------+-------+------+
| license_plate | brand | model | color | year |
+---------------+-----------+------------+-------+------+
| ABC123 | Ford | Mustang | Red | 2018 |
| CES214 | Ford | Mustang | Red | 2018 |
| DEF456 | Chevrolet | Camaro | Blue | 2016 |
| GHI789 | Dodge | Challenger | Black | 2014 |
+---------------+-----------+------------+-------+------+

第一行和第二行都描述了一辆红色的 2018 款福特野马。仅凭车辆的品牌和型号无法唯一识别该车。车牌号在这两例中都不同,因此可以为表中的每一行提供一个很好的唯一标识符。由于车牌号已经是数据的一部分,将其用作主键可以创建一个自然键。如果在创建表时未在 License_plate 列上使用主键,则数据集中可能会出现重复页或空页。.

接下来,创建一个与上面类似的表,以 License_plate 列作为主键,并包含以下列:

  • License_plate:此列包含车牌号码,以 varchar 数据类型表示。.
  • 品牌:此列表示汽车品牌,使用 varchar 数据类型表示,最多 50 个字符。.
  • 型号:此列包含汽车型号,使用 varchar 数据类型表示,最多 50 个字符。.
  • 颜色:此列存储颜色,使用 varchar 数据类型表示,最多 20 个字符。.
  • 年份:此列显示汽车的生产年份,使用 int 数据类型存储数值数据。.

要创建 machines 表,请运行以下 SQL 语句:

CREATE TABLE cars (
license_plate varchar(8) PRIMARY KEY,
brand varchar(50),
model varchar(50),
color varchar(20),
year int
);... */

PRIMARY KEY 语句遵循 License_plate 数据类型的定义。当处理基于单列的主键时,可以使用简化的语法创建键,并将主键写入列定义中。.

如果打印出以下输出,则表示表格已创建:

Output
Query OK, 0 rows affected (0.00 sec)

之后,通过执行以下 INSERT INTO 操作,将上面示例中提供的示例行加载到表中:

INSERT INTO cars VALUES
('ABC123', 'Ford', 'Mustang', 'Red', 2018),
('CES214', 'Ford', 'Mustang', 'Red', 2018),
('DEF456', 'Chevrolet', 'Camaro', 'Blue', 2016),
('GHI789', 'Dodge', 'Challenger', 'Black', 2014);

数据库将返回成功消息:

Output
Query OK, 4 rows affected (0.010 sec)
Records: 4 Duplicates: 0 Warnings: 0

现在可以使用 SELECT 语句验证新创建的表是否包含预期的数据和格式:

SELECT * FROM cars;

输出结果显示一个与本节开头类似的表格:

Output
+---------------+-----------+------------+-------+------+
| license_plate | brand | model | color | year |
+---------------+-----------+------------+-------+------+
| ABC123 | Ford | Mustang | Red | 2018 |
| CES214 | Ford | Mustang | Red | 2018 |
| DEF456 | Chevrolet | Camaro | Blue | 2016 |
| GHI789 | Dodge | Challenger | Black | 2014 |
+---------------+-----------+------------+-------+------+

接下来,您可以检查数据库引擎是否保证主键规则的有效性。尝试插入一辆车牌号重复的车辆,运行以下命令:

INSERT INTO cars VALUES ('DEF456', 'Jeep', 'Wrangler', 'Yellow', 2019);

MySQL 返回错误消息,指出 DEF456 标签导致主键存在重复条目:

Output
ERROR 1062 (23000): Duplicate entry 'DEF456' for key 'cars.PRIMARY'

注意:主键底层是通过唯一索引实现的,它与您手动为表中其他列创建的索引有很多相同的特性。最重要的是,主键索引还能提高针对定义索引的列的查询性能。有关如何使用索引的更多信息,请参阅本教程中的“如何使用索引”指南。.

现在您可以确定重复车牌是不允许的。接下来,请检查空白车牌的车辆是否可以进口:

INSERT INTO cars VALUES (NULL, 'Jeep', 'Wrangler', 'Yellow', 2019);

这一次,数据库将返回另一条错误信息:

Output
ERROR 1048 (23000): Column 'license_plate' cannot be null

数据库强制执行这两条规则后,可以确保 License_plate 唯一标识表中的每一行。如果针对每个车牌号查询该表,每次查询都会返回一行数据。.

下一节,您将学习如何使用多列主键。.

创建跨多列的主键

当一列不足以唯一标识表中的一行时,您可以创建使用多列的主键。.

例如,想象一下这样的房屋登记册:仅凭街道名称或街道号码都不足以识别每一栋房屋:

Sample table
+-------------------+---------------+-------------------+------+
| street_name | street_number | house_owner | year |
+-------------------+---------------+-------------------+------+
| 5th Avenue | 100 | Bob Johnson | 2018 |
| Broadway | 1500 | Jane Smith | 2016 |
| Central Park West | 100 | John Doe | 2014 |
| Central Park West | 200 | Tom Thompson | 2015 |
| Lexington Avenue | 5001 | Samantha Davis | 2010 |
| Park Avenue | 7000 | Michael Rodriguez | 2012 |
+-------------------+---------------+-------------------+------+

表格中,“中央公园西路”这个街道名称出现了不止一次,门牌号“100”也出现了不止一次。但是,没有发现重复的街道名称和门牌号组合。在这种情况下,虽然这两列都不能作为主键,但这两个值的组合可以用来唯一标识表格中的每一行。.

接下来,创建一个类似于上图所示的表格,包含以下列:

  • street_name:此列表示房屋所在街道的名称,varchar 数据类型限制为 50 个字符。.
  • street_number:此列存储房屋的街道号码,数据类型为 varchar。此列最多可存储 5 个字符。它不使用 int 数值数据类型,因为有些街道号码可能包含字母(例如 200B)。.
  • house_owner:此列包含房屋所有者的姓名,以限制为 50 个字符的 varchar 数据类型表示。.
  • 年份:此列表示房屋的建造年份,使用 int 数据类型存储数值。.

这次,主键同时使用了 street_name 和 street_number 列,而不是只使用其中一列。为此,请运行以下 SQL 语句:

CREATE TABLE houses (
street_name varchar(50),
street_number varchar(5),
house_owner varchar(50),
year int,
PRIMARY KEY(street_name, street_number)
);

与之前的示例不同,这次 PRIMARY KEY 语句出现在列定义下方。PRIMARY KEY 语句用括号括起来,包含两个列名:street_name 和 street_number。这种语法在 houses 表中创建了一个主键,该主键由两列组成。.

如果打印出以下输出,则表示表格已创建:

Output
Query OK, 0 rows affected (0.00 sec)

之后,通过执行以下 INSERT INTO 操作,将前面示例中提供的示例行加载到表中:

INSERT INTO houses VALUES
('Central Park West', '100', 'John Doe', 2014),
('Broadway', '1500', 'Jane Smith', 2016),
('5th Avenue', '100', 'Bob Johnson', 2018),
('Lexington Avenue', '5001', 'Samantha Davis', 2010),
('Park Avenue', '7000', 'Michael Rodriguez', 2012),
('Central Park West', '200', 'Tom Thompson', 2015);

数据库将返回成功消息:

Output
Query OK, 6 rows affected (0.000 sec)
Records: 6 Duplicates: 0 Warnings: 0

现在可以使用 SELECT 语句验证新创建的表是否包含预期的数据和格式:

SELECT * FROM houses;

输出结果显示一个与本节开头类似的表格:

Output
+-------------------+---------------+-------------------+------+
| street_name | street_number | house_owner | year |
+-------------------+---------------+-------------------+------+
| 5th Avenue | 100 | Bob Johnson | 2018 |
| Broadway | 1500 | Jane Smith | 2016 |
| Central Park West | 100 | John Doe | 2014 |
| Central Park West | 200 | Tom Thompson | 2015 |
| Lexington Avenue | 5001 | Samantha Davis | 2010 |
| Park Avenue | 7000 | Michael Rodriguez | 2012 |
+-------------------+---------------+-------------------+------+
6 rows in set (0.000 sec)

现在,我们来检查一下数据库是否允许街道名称和门牌号重复,但限制完整地址重复出现在表中。我们先在公园街上添加另一栋房子:

INSERT INTO houses VALUES ('Park Avenue', '8000', 'Emily Brown', 2011);

MySQL 返回成功消息,因为地址 8000 Park Avenue 之前并未出现在表中:

Output
Query OK, 1 row affected (0.010 sec)

当你在主街 8000 号添加一栋房子并重复使用街道号码时,也会出现类似的结果:

INSERT INTO houses VALUES ('Main Street', '8000', 'David Jones', 2009);

同样,这样做可以正确插入新行,因为地址没有重复:

Output
Query OK, 1 row affected (0.010 sec)

但是,请使用以下 INSERT 语句在第五大道 100 号添加另一栋房屋:

INSERT INTO houses VALUES ('5th Avenue', '100', 'Josh Gordon', 2008);

数据库返回错误消息,告知您主键中存在重复条目,其值对为 5th Avenue 和 100:

Output
ERROR 1062 (23000): Duplicate entry '5th Avenue-100' for key 'houses.PRIMARY'

数据库正确地执行了主键规则,主键定义在一对列上。您可以放心,完整的地址(包括街道名称和门牌号)不会在表中重复出现。.

在本节中,您创建了一个包含两列的自然键,用于唯一标识 home 表中的每一行。但是,主键并非总是可以从数据集中提取的。在下一节中,您将使用并非直接来自数据的人工主键。.

创建顺序主键

到目前为止,您已经使用示例数据集中的列创建了唯一的主键。但在某些情况下,数据不可避免地会重复,导致列无法作为有效的唯一标识符。在这种情况下,您可以使用生成的标识符创建顺序主键。当您的数据需要创建新的标识符来标识行时,基于这些人工标识符创建的主键称为代理键。.

想象一下读书俱乐部成员名单——这是一个非正式的聚会,任何人都可以加入,无需出示政府颁发的身份证件。很可能在某个时候,名字相似的人会加入这个俱乐部:

Sample table
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| John | Doe |
| Jane | Smith |
| Bob | Johnson |
| Samantha | Davis |
| Michael | Rodriguez |
| Tom | Thompson |
| Sara | Johnson |
| David | Jones |
| Jane | Smith |
| Bob | Johnson |
+------------+-----------+

表格中出现了重复的 Bob Johnson 和 Jane Smith 这两个名字。你需要使用额外的标识符来区分他们,而且目前无法唯一标识表格中的每一行。如果你用纸质方式记录读书会成员名单,你可以使用辅助标识符来帮助区分姓名相似的人。.

在关系数据库中,您也可以通过添加一个额外的列来实现类似的功能,该列存储生成的非真实 ID,其唯一目的是唯一地隔离表中的所有行。我们将其称为 Member_id。.

然而,每次想在数据库中添加新的读书俱乐部成员时,都要创建这样的标识符,这很麻烦。为了解决这个问题,MySQL 提供了数值列自增功能,数据库会自动按整数序列递增列值。.

让我们创建一个与上面类似的表。您需要添加一个自增列(member_id),用于存储每个俱乐部成员自动分配的编号。该自动分配的编号将作为表的主键:

Member_id:此列具有自增数字标识符,由 int 数据类型表示。.

first_name:此列包含俱乐部成员的名字,以限制为 50 个字符的 varchar 数据类型表示。.

last_name:此列保存俱乐部成员的姓氏,以限制为 50 个字符的 varchar 数据类型显示。.

要创建该表,请运行以下 SQL 语句:

CREATE TABLE club_members (
member_id int AUTO_INCREMENT PRIMARY KEY,
first_name varchar(50),
last_name varchar(50)
);

虽然 PRIMARY KEY 语句出现在列类型定义之后(就像单列主键一样),但它前面还有一个额外的属性:AUTO_INCREMENT。它告诉 MySQL,如果没有显式提供值,则使用递增的数字序列自动为该列生成值。.

注意:列定义的 AUTO_INCREMENT 属性是 MySQL 特有的。其他数据库通常也提供类似的生成顺序键的方法,但语法因数据库引擎而异。如有疑问,建议查阅您所用关系数据库管理系统的官方文档。.

如果打印出以下输出,则表示表格已创建:

Output
Query OK, 0 rows affected (0.00 sec)

之后,通过执行以下 INSERT INTO 操作,将上面示例中提供的示例行加载到表中:

INSERT INTO club_members (first_name, last_name) VALUES
('John', 'Doe'),
('Jane', 'Smith'),
('Bob', 'Johnson'),
('Samantha', 'Davis'),
('Michael', 'Rodriguez'),
('Tom', 'Thompson'),
('Sara', 'Johnson'),
('David', 'Jones'),
('Jane', 'Smith'),
('Bob', 'Johnson');

INSERT 语句现在包含列名列表(first_name 和 last_name),这确保数据库知道数据集中未提供 Member_id 列,因此应该使用其默认值。.

数据库将返回成功消息:

Output
Query OK, 10 rows affected (0.002 sec)
Records: 10 Duplicates: 0 Warnings: 0

使用 SELECT 语句验证新创建的表中的数据:

SELECT * FROM club_members;

输出结果显示一个与本节开头类似的表格:

Output
+-----------+------------+-----------+
| member_id | first_name | last_name |
+-----------+------------+-----------+
| 1 | John | Doe |
| 2 | Jane | Smith |
| 3 | Bob | Johnson |
| 4 | Samantha | Davis |
| 5 | Michael | Rodriguez |
| 6 | Tom | Thompson |
| 7 | Sara | Johnson |
| 8 | David | Jones |
| 9 | Jane | Smith |
| 10 | Bob | Johnson |
+-----------+------------+-----------+
10 rows in set (0.000 sec)

然而,这一次,结果中出现了 Member_id 列,其中包含 1 到 10 的数字序列。有了这一列,Jane Smith 和 Bob Johnson 的重复行就无法识别了,因为每个名字都与一个唯一的标识符 (Member_id) 相关联。.

现在,让我们检查一下数据库是否允许我们将另一位汤姆·汤普森添加到俱乐部会员名单中:

INSERT INTO club_members (first_name, last_name) VALUES ('Tom', 'Thompson');

MySQL 将返回成功消息:

Output
Query OK, 1 row affected (0.009 sec)

要检查数据库为新条目分配的数字 ID,请再次运行 SELECT 查询:

SELECT * FROM club_members;

输出结果中还有一行:

Output
+-----------+------------+-----------+
| member_id | first_name | last_name |
+-----------+------------+-----------+
| 1 | John | Doe |
| 2 | Jane | Smith |
| 3 | Bob | Johnson |
| 4 | Samantha | Davis |
| 5 | Michael | Rodriguez |
| 6 | Tom | Thompson |
| 7 | Sara | Johnson |
| 8 | David | Jones |
| 9 | Jane | Smith |
| 10 | Bob | Johnson |
| 11 | Tom | Thompson |
+-----------+------------+-----------+
11 rows in set (0.000 sec)

通过数据库的 AUTO_INCREMENT 属性,自动为 Member_id 列中的新行分配了数字 11。.

如果您正在处理的数据没有天然的主键候选对象,并且您不想每次向数据库添加新数据时都想出人为的标识符,则可以安全地依赖顺序生成的标识符作为主键。.

结果

通过本指南,您学习了什么是主键,以及如何在 MySQL 中创建常用类型的主键来标识数据库表中的唯一行。您创建了自然主键、跨多列的主键,并在不存在自然主键的情况下使用了自增顺序键。.

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

您可能也喜欢