介绍
使用数据库的最基本部分之一是检索有关其中保存的数据的信息的实践。 在关系数据库管理系统中,用于从表中检索信息的任何操作都称为 查询 。
在本指南中,我们将讨论 结构化查询语言 (SQL) 中的查询语法以及一些更常用的函数和运算符。
先决条件
为了遵循本指南,您需要一台运行某种类型的使用 SQL 的关系数据库管理系统 (RDBMS) 的计算机。 本指南中的说明和示例已使用以下环境进行验证:
- 运行 Ubuntu 20.04 的服务器,具有管理权限的非 root 用户和配置了 UFW 的防火墙,如我们的 Ubuntu 20.04 初始服务器设置指南中所述。
- 如 如何在 Ubuntu 20.04 上安装 MySQL 中所述,在服务器上安装并保护了 MySQL。 如 Step 3 中所述,此备忘单已使用新创建的用户进行验证。
注意:请注意,许多 RDBMS 使用自己独特的 SQL 实现。 尽管本教程中概述的命令适用于大多数 RDBMS,但如果您在 MySQL 以外的系统上测试它们,确切的语法或输出可能会有所不同。
- 您还需要一个数据库,其中包含一些加载了示例数据的表,您可以使用这些表来练习编写查询。 我们鼓励您阅读以下 连接到 MySQL 并设置示例数据库 部分,详细了解如何连接到 MySQL 服务器并创建本指南示例中使用的测试数据库。
您还可以使用嵌入在此页面上的交互式终端来试验本教程中的示例查询。 单击下面的Launch an Interactive Terminal!
按钮开始使用。
启动交互式终端!
连接到 MySQL 并设置示例数据库
如果您的 SQL 数据库系统在远程服务器上运行,请从本地计算机通过 SSH 连接到您的服务器:
ssh sammy@your_server_ip
然后打开 MySQL 服务器提示符,将 sammy
替换为您的 MySQL 用户帐户的名称。 如果您在此页面上使用嵌入式交互式终端,请注意提示时使用的密码是单词 secret
:
mysql -u sammy -p
根据提示,创建一个名为 queries_db
的数据库:
CREATE DATABASE queries_db;
如果数据库创建成功,您将收到如下输出:
OutputQuery OK, 1 row affected (0.01 sec)
要选择 queries_db
数据库,请运行以下 USE
语句:
USE queries_db;
OutputDatabase changed
选择 queries_db
后,在其中创建几个表。
为了遵循本指南中使用的示例,假设您在纽约市开展了一项公园清理计划。 该计划由志愿者组成,他们承诺通过定期清理垃圾来清理他们家附近的城市公园。 加入该倡议后,这些志愿者每个人都设定了一个目标,即他们希望每周捡起多少垃圾袋。 您决定将有关志愿者目标的信息存储在一个 SQL 数据库中,该数据库具有一个包含五列的表:
vol_id
:每个志愿者的识别号,用int
数据类型表示。 此列将用作表的 主键 ,这意味着每个值将作为其各自行的唯一标识符。 因为主键中的每个值都必须是唯一的,所以该列也将应用UNIQUE
约束name
:每个志愿者的名字,用varchar
数据类型表示,最多20个字符park
:每个志愿者捡拾垃圾的公园名称,用varchar
数据类型表示,最多20个字符。 请注意,多个志愿者可以在同一个公园清理垃圾weekly_goal
:每个志愿者的目标是他们希望在一周内捡起多少袋垃圾,用int
类型表示max_bags
:每个志愿者在一周内捡到的垃圾袋数的个人记录,表示为int
运行以下 CREATE TABLE
语句以创建一个名为 volunteers
的表,其中包含以下五列:
CREATE TABLE volunteers ( vol_id int UNIQUE, name varchar(20), park varchar(30), weekly_goal int, max_bags int, PRIMARY KEY (vol_id) );
然后用一些示例数据加载 volunteers
表。 运行以下 INSERT INTO
操作以添加代表七名程序志愿者的七行数据:
INSERT INTO volunteers VALUES (1, 'Gladys', 'Prospect Park', 3, 5), (2, 'Catherine', 'Central Park', 2, 2), (3, 'Georgeanna', 'Central Park', 2, 1), (4, 'Wanda', 'Van Cortland Park', 1, 1), (5, 'Ann', 'Prospect Park', 2, 7), (6, 'Juanita', 'Riverside Park', 1, 4), (7, 'Georgia', 'Prospect Park', 1, 3);
有了它,您就可以按照本指南的其余部分开始学习如何在 SQL 中创建查询了。
必需的查询组件:SELECT
和 FROM
子句
在 SQL 中, 语句 是发送到数据库系统的任何操作,它将执行某种任务,例如创建表、插入或删除数据,或更改列或表的结构。 query 是一个 SQL 语句,用于检索有关数据库中保存的数据的信息。
就其本身而言,查询不会更改表中保存的任何现有数据。 它只会返回有关查询作者明确请求的数据的信息。 给定查询返回的信息称为其 结果集 。 结果集通常由指定表中的一列或多列组成,结果集中返回的每一列都可以包含一行或多行信息。
下面是 SQL 查询的一般语法:
SELECT columns_to_return FROM table_to_query;
SQL 语句由各种 子句 组成,这些子句由某些关键字和这些关键字所需的信息组成。 SQL 查询至少需要包含两个子句:SELECT
和 FROM
子句。
注意:在这个示例语法中,两个子句都写在自己的行上。 但是,任何 SQL 语句也可以写在一行上,如下所示:
SELECT columns_to_return FROM table_to_query;
本指南将遵循将语句分隔为多行的常见 SQL 样式约定,因此每行仅包含一个子句。 这旨在使每个示例更具可读性和可理解性,但请注意,只要您不包含任何语法错误,您就可以在一行或任意多行上编写任何查询。
每个 SQL 查询都以 SELECT
子句开头,导致一些人通常将查询称为 SELECT statements。 在 SELECT
关键字之后是您希望在结果集中返回的任何列的列表。 这些列是从 FROM
子句中指定的表中提取的。
在 SQL 查询中,执行顺序从 FROM
子句开始。 这可能会造成混淆,因为 SELECT
子句是在 FROM
子句之前编写的,但请记住,RDBMS 在开始从中检索信息之前必须首先知道要查询的完整工作数据集. 将查询视为 SELECT
- 指定表中的指定列 FROM
可能会有所帮助。 最后,需要注意的是,每条 SQL 语句都必须以分号 (;
) 结尾。
例如,运行以下查询。 这将从 volunteers
表中检索 name
列:
SELECT name FROM volunteers;
这是此查询的结果集:
Output+------------+ | name | +------------+ | Gladys | | Catherine | | Georgeanna | | Wanda | | Ann | | Juanita | | Georgia | +------------+ 7 rows in set (0.00 sec)
即使此操作查看了整个 volunteers
表,它也只返回指定的列 name
。
您可以通过用逗号分隔每个列的名称来从多个列中检索信息,如以下查询所示。 这将返回 volunteers
表中的 vol_id
、name
和 park
列:
SELECT park, name, vol_id FROM volunteers;
Output+-------------------+------------+--------+ | park | name | vol_id | +-------------------+------------+--------+ | Prospect Park | Gladys | 1 | | Central Park | Catherine | 2 | | Central Park | Georgeanna | 3 | | Van Cortland Park | Wanda | 4 | | Prospect Park | Ann | 5 | | Riverside Park | Juanita | 6 | | Prospect Park | Georgia | 7 | +-------------------+------------+--------+ 7 rows in set (0.00 sec)
请注意,此结果集首先返回 park
列,然后是 name
列,然后是 vol_id
。 SQL 数据库通常会以列在 SELECT
子句中的任何顺序返回列。
有时您可能想要从表中检索每一列。 您可以输入一个星号 (*
),而不是写出查询中每一列的名称。 在 SQL 中,这是“每一列”的简写。
以下查询将返回 volunteers
表中的每一列:
SELECT * FROM volunteers;
Output+--------+------------+-------------------+-------------+----------+ | vol_id | name | park | weekly_goal | max_bags | +--------+------------+-------------------+-------------+----------+ | 1 | Gladys | Prospect Park | 3 | 5 | | 2 | Catherine | Central Park | 2 | 2 | | 3 | Georgeanna | Central Park | 2 | 1 | | 4 | Wanda | Van Cortland Park | 1 | 1 | | 5 | Ann | Prospect Park | 2 | 7 | | 6 | Juanita | Riverside Park | 1 | 4 | | 7 | Georgia | Prospect Park | 1 | 3 | +--------+------------+-------------------+-------------+----------+ 7 rows in set (0.00 sec)
请注意,此结果集的列是如何按照在前面的 连接到 MySQL 并设置示例数据库 部分的 CREATE TABLE
语句中定义的相同顺序列出的。 这是大多数关系数据库系统在运行使用星号代替单个列名的查询时对结果集中的列进行排序的方式。
请注意,您可以使用 JOIN
关键字从同一查询中的多个表中检索信息。 我们鼓励您遵循我们关于 如何在 SQL 中使用联接的指南,了解如何执行此操作的详细信息。
使用 DISTINCT
删除重复值
默认情况下,RDBMS 将返回查询返回的列中的每个值,包括重复值。
例如,运行以下查询。 这将返回 volunteers
表的 park
列中的值:
SELECT park FROM volunteers;
Output+-------------------+ | park | +-------------------+ | Prospect Park | | Central Park | | Central Park | | Van Cortland Park | | Prospect Park | | Riverside Park | | Prospect Park | +-------------------+ 7 rows in set (0.00 sec)
请注意此结果集如何包含两个重复值:Prospect Park
和 Central Park
。 这是有道理的,因为多个志愿者可以在同一个公园清理垃圾。 但是,有时您可能只想知道列中包含哪些唯一值。 您可以通过在 SELECT
后面加上 DISTINCT
关键字来发出删除重复值的查询。
以下查询将返回 parks
列中的每个唯一值,并删除所有重复项。 除了包含 DISTINCT
关键字之外,它与前面的查询相同:
SELECT DISTINCT park FROM volunteers;
Output+-------------------+ | park | +-------------------+ | Prospect Park | | Central Park | | Van Cortland Park | | Riverside Park | +-------------------+ 4 rows in set (0.00 sec)
此查询的结果集比前一个少三行,因为它删除了 Central Park
值之一和 Prospect Park
值中的两个。
请注意,SQL 将结果集的每一行视为单独的记录,并且 DISTINCT
仅在多行在每列中共享相同值时才会消除重复项
为了说明这一点,发出以下包含 DISTINCT
关键字但返回 name
和 park
列的查询:
SELECT DISTINCT name, park FROM volunteers;
Output+------------+-------------------+ | name | park | +------------+-------------------+ | Gladys | Prospect Park | | Catherine | Central Park | | Georgeanna | Central Park | | Wanda | Van Cortland Park | | Ann | Prospect Park | | Juanita | Riverside Park | | Georgia | Prospect Park | +------------+-------------------+ 7 rows in set (0.00 sec)
park
列中的重复值 — 三个 Prospect Park
和两个 Central Park
— 出现在此结果集中,即使查询包含 DISTINCT
关键词。 尽管结果集中的各个列可能包含重复值,但整行必须与另一行完全相同,才能被 DISTINCT
删除。 在这种情况下,name
列中的每个值都是唯一的,因此当在 SELECT
子句中指定该列时,DISTINCT
不会删除任何行。
使用 WHERE
子句过滤数据
有时您可能希望从数据库中的表中检索更精细的信息。 您可以通过在 FROM
子句之后的查询中包含 WHERE
子句来过滤某些行,如下所示:
SELECT columns_to_return FROM table_to_query WHERE search_condition;
此示例语法中的 WHERE
关键字后面是 搜索条件 ,它实际上决定了从结果集中过滤掉哪些行。 搜索条件是一组一个或多个 谓词 或可以计算一个或多个值表达式的表达式。 在 SQL 中, 值表达式 — 有时也称为 标量表达式 — 是任何将返回单个值的表达式。 值表达式可以是文字值(如字符串或数值)、数学表达式或列名。
WHERE
子句搜索条件中的谓词可以采用多种形式,但它们通常遵循以下语法:
. . . WHERE value expression OPERATOR value_expression . . .
在 WHERE
关键字之后,您提供一个值表达式,后跟几个特殊 SQL 运算符之一,这些运算符用于根据该运算符之后的值表达式(或多个值表达式)计算列的值。 SQL 中有几个这样的运算符可用,本指南将在本节后面简要介绍其中一些,但为了说明目的,它只关注最常用的运算符之一:等号(=
)。 此运算符测试两个值表达式是否等价。
谓词总是返回“真”、“假”或“未知”的结果。 当运行包含 WHERE
子句的 SQL 查询时,DBMS 将按顺序将搜索条件应用于 FROM
子句中定义的表中的每一行。 它只会返回搜索条件中每个谓词计算结果为“真”的行。
为了说明这个想法,运行以下 SELECT
语句。 此查询从 volunteers
表的 name
列返回值。 然而,这个 WHERE
子句不是评估来自表的列之一的值,而是测试两个值表达式 - (2 + 2)
和 4
- 是否等价:
SELECT name FROM volunteers WHERE (2 + 2) = 4;
因为 (2 + 2)
是 always 等于 4
,因此对于表中的每一行,此搜索条件的计算结果为“真”。 因此,每一行的 name
值都会在结果集中返回:
Output+------------+ | name | +------------+ | Gladys | | Catherine | | Georgeanna | | Wanda | | Ann | | Juanita | | Georgia | +------------+ 7 rows in set (0.00 sec)
因为这个搜索条件总是返回“真”的结果,所以它不是很有用。 您也可以根本不包含 WHERE
子句,因为 SELECT name FROM volunteers;
将产生相同的结果集。
您通常不会像这样比较两个文字值,而是使用列名作为 WHERE
子句搜索条件中的值表达式之一。 通过这样做,您告诉数据库管理系统使用该列中每一行的值作为该行的搜索条件迭代的值表达式。
以下查询的 WHERE
子句将更排他的搜索条件应用于每一行。 它将从 max_bags
值等于 4
的任何行返回 name
和 max_bags
值:
SELECT name, max_bags FROM volunteers WHERE max_bags = 4;
只有一名志愿者的 max_bags
值恰好等于 4
,因此查询只返回该志愿者的记录:
Output+---------+----------+ | name | max_bags | +---------+----------+ | Juanita | 4 | +---------+----------+ 1 row in set (0.00 sec)
您还可以评估搜索条件谓词中的字符串值。 以下查询返回 name
值等于 'Wanda'
的每一行的 vol_id
和 name
值:
SELECT vol_id, name FROM volunteers WHERE name = 'Wanda';
因为只有一个名为 Wanda
的志愿者,所以查询只返回该行的信息:
Output+--------+-------+ | vol_id | name | +--------+-------+ | 4 | Wanda | +--------+-------+ 1 row in set (0.00 sec)
重申一下,本节的示例都使用相同的搜索条件运算符 - 等号 - 来过滤数据。 但是,还有许多其他类型的运算符允许您编写各种谓词,从而对查询返回的信息提供高级别的控制。
SQL 标准定义了 18 种不同类型的谓词,但并不是每个 RDBMS 都支持所有谓词。 以下是五种最常用的搜索条件谓词类型及其使用的运算符:
Comparison:比较谓词将一个值表达式与另一个值表达式进行比较; 在查询中,这些值表达式中至少有一个是列名的情况几乎总是如此。 六个比较运算符是:
=
:测试两个值是否相等<>
:测试两个值是否不等价<
:测试第一个值是否小于第二个>
:测试第一个值是否大于第二个<=
:测试第一个值是否小于或等于第二个>=
:测试第一个值是否大于等于第二个
Null:使用 IS NULL
运算符的谓词测试给定列中的值是否为 Null Range:范围谓词使用 BETWEEN
运算符测试是否一个值表达式落在另外两个之间 Membership:这种类型的谓词使用 IN
运算符来测试一个值是否是给定集合的成员 模式匹配:模式匹配谓词使用 LIKE
运算符来测试值是否匹配包含通配符值的字符串模式
更详细地介绍每种谓词类型超出了本教程的范围。 但是,如果您想了解有关它们的更多信息,我们鼓励您查看以下指南:
要了解有关 WHERE
子句的更多信息,请参阅我们的 如何在 SQL 中使用 WHERE 子句的指南。
使用 ORDER BY
对查询结果进行排序
有时查询会以不直观的方式返回信息,或者可能不适合您的特定需求。 您可以通过将 ORDER BY
子句附加到查询语句的末尾来对查询结果进行排序。
以下是带有 ORDER BY
子句的查询的一般语法:
SELECT columns_to_return FROM table_to_query ORDER BY column_name;
为了说明这是如何工作的,假设您想知道哪些志愿者的 max_bags
值最高。 您可以运行以下查询,该查询返回 volunteers
表中的 name
和 max_bags
值:
SELECT name, max_bags FROM volunteers;
但是,此查询按添加每一行的顺序对结果集进行排序:
Output+------------+----------+ | name | max_bags | +------------+----------+ | Gladys | 5 | | Catherine | 2 | | Georgeanna | 1 | | Wanda | 1 | | Ann | 7 | | Juanita | 4 | | Georgia | 3 | +------------+----------+ 7 rows in set (0.00 sec)
对于这样一个相对较小的数据集,结果集的顺序并不那么重要,您只需扫描此结果集的 max_bags
值即可找到最高的值。 但是,当处理大量数据时,这很快就会变得乏味。
相反,您可以运行相同的查询,但添加一个 ORDER BY
子句,该子句根据每行的 max_bags
值对结果集进行排序:
SELECT name, max_bags FROM volunteers ORDER BY max_bags;
Output+------------+----------+ | name | max_bags | +------------+----------+ | Georgeanna | 1 | | Wanda | 1 | | Catherine | 2 | | Georgia | 3 | | Juanita | 4 | | Gladys | 5 | | Ann | 7 | +------------+----------+ 7 rows in set (0.00 sec)
如该输出所示,包含 ORDER BY
子句的 SQL 查询的默认行为是以升序(递增)顺序对指定列的值进行排序。 您可以通过将 DESC
关键字附加到 ORDER BY
子句来更改此行为并按降序对它们进行排序:
SELECT name, max_bags FROM volunteers ORDER BY max_bags DESC;
Output+------------+----------+ | name | max_bags | +------------+----------+ | Ann | 7 | | Gladys | 5 | | Juanita | 4 | | Georgia | 3 | | Catherine | 2 | | Georgeanna | 1 | | Wanda | 1 | +------------+----------+ 7 rows in set (0.00 sec)
结论
通过阅读本指南,您了解了如何编写基本查询以及过滤和排序查询结果集。 虽然此处显示的命令应该适用于大多数关系数据库,但请注意,每个 SQL 数据库都使用自己独特的语言实现。 您应该查阅您的 DBMS 的官方文档,以获得对每个命令及其全套选项的更完整描述。
如果您想了解有关使用 SQL 的更多信息,我们鼓励您查看本系列中关于 如何使用 SQL 的其他教程。