介绍
使用数据库的最基本部分之一是检索有关其中保存的数据的信息的做法。在关系数据库管理系统中,用于从表中检索信息的任何操作都称为查询。
在本指南中,我们将讨论结构化查询语言 (SQL)中查询的语法及其一些更常用的函数和运算符。
先决条件
为了遵循本指南,您需要一台运行某种类型的使用 SQL 的关系数据库管理系统 (RDBMS) 的计算机。本指南中的说明和示例已使用以下环境进行验证:
- 运行 Ubuntu 20.04 的服务器,具有管理权限的非 root 用户和使用 UFW 配置的防火墙,如我们的 Ubuntu 20.04 初始服务器设置指南中所述。
- 在服务器上安装并保护 MySQL,如如何在 Ubuntu 20.04 上安装 MySQL 中所述。如步骤 3 中所述,此备忘单已使用新创建的用户进行验证。
注意:请注意,许多 RDBMS 使用自己独特的 SQL 实现。尽管本教程中概述的命令适用于大多数 RDBMS,但如果您在 MySQL 以外的系统上测试它们,则确切的语法或输出可能会有所不同。
- 您还需要一个数据库,其中包含一些加载了示例数据的表,您可以使用它们来练习编写查询。我们鼓励您阅读以下连接到 MySQL 和设置示例数据库部分,详细了解如何连接到 MySQL 服务器并创建本指南示例中使用的测试数据库。
连接到 MySQL 并设置示例数据库
如果您的 SQL 数据库系统在远程服务器上运行,请从本地计算机通过 SSH 连接到您的服务器:
- ssh sammy@your_server_ip
然后打开数据库服务器提示。如果您使用的是 MySQL,请运行以下命令,确保替换sammy
为您的 MySQL 用户帐户的名称:
- 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
andFROM
子句
在 SQL 中,语句是发送到数据库系统的任何操作,这些操作将执行某种任务,例如创建表、插入或删除数据或更改列或表的结构。一个查询是一个SQL语句有关数据检索信息在数据库中举行。
就其本身而言,查询不会更改表中保存的任何现有数据。它只会返回有关查询作者明确请求的数据的信息。给定查询返回的信息称为其结果集。结果集通常由指定表中的一列或多列组成,结果集中返回的每一列可以包含一行或多行信息。
这是 SQL 查询的一般语法:
- SELECT columns_to_return
- FROM table_to_query;
SQL 语句由各种子句组成,这些子句由某些关键字和这些关键字所需要的信息组成。至少,SQL 查询只需要您包含两个子句:SELECT
andFROM
子句。
注意:在此示例语法中,两个子句都写在自己的行上。但是,任何 SQL 语句也可以写在一行上,如下所示:
- SELECT columns_to_return FROM table_to_query;
本指南将遵循将语句分成多行的通用 SQL 样式约定,因此每行仅包含一个子句。这旨在使每个示例更具可读性和可理解性,但请注意,只要您不包含任何语法错误,您就可以在一行或任意多行中编写任何查询。
每个 SQL 查询都以一个SELECT
子句开头,导致一些人将查询一般称为SELECT
statements。在SELECT
关键字之后是您想要在结果集中返回的任何列的列表。这些列是从FROM
子句中指定的表中提取的。
在 SQL 查询中,执行顺序从FROM
子句开始。这可能会令人困惑,因为SELECT
子句写在FROM
子句之前,但请记住,RDBMS 必须首先知道要查询的完整工作数据集,然后才能开始从中检索信息。将查询视为 –指定表中SELECT
的指定列会很有帮助FROM
。最后,需要注意的是,每条 SQL 语句都必须以分号 ( ;
)结尾。
例如,运行以下查询。这将从表中检索name
列volunteers
:
- SELECT name
- FROM volunteers;
这是此查询的结果集:
Output+------------+
| name |
+------------+
| Gladys |
| Catherine |
| Georgeanna |
| Wanda |
| Ann |
| Juanita |
| Georgia |
+------------+
7 rows in set (0.00 sec)
即使此操作查看了整个volunteers
表,它也只返回指定的列name
。
您可以通过用逗号分隔每个列的名称来从多个列中检索信息,如以下查询所示。这将返回vol_id
,name
和park
从列volunteers
表:
- 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)
请注意此结果集的列是如何按照它们CREATE TABLE
在前面的连接到 MySQL 和设置示例数据库部分的语句中定义的相同顺序列出的。这是大多数关系数据库系统在运行使用星号代替单个列名的查询时对结果集中的列进行排序的方式。
请注意,您可以使用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
关键字但同时返回 thename
和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
列中的重复值(出现 3 次Prospect Park
和 2 次)Central Park
也会出现在此结果集中DISTINCT
。尽管结果集中的个别列可能包含重复值,但整行必须与另一行完全相同才能被 删除DISTINCT
。在这种情况下,name
列中的每个值都是唯一的,因此DISTINCT
在SELECT
子句中指定该列时不会删除任何行。
用WHERE
子句过滤数据
有时您可能希望从数据库中的表中检索更精细的信息。您可以通过WHERE
在查询中的FROM
子句之后包含一个子句来过滤掉某些行,如下所示:
- 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)
是总是相等4
,该搜索条件计算为“真”为表中的每一行。因此,每一行的name
值都会在结果集中返回:
Output+------------+
| name |
+------------+
| Gladys |
| Catherine |
| Georgeanna |
| Wanda |
| Ann |
| Juanita |
| Georgia |
+------------+
7 rows in set (0.00 sec)
因为这个搜索条件总是返回“true”的结果,所以它不是很有用。您也可以根本不包含WHERE
子句,因为SELECT name FROM volunteers;
会产生相同的结果集。
不是像这样比较两个文字值,您通常会使用列名作为WHERE
子句搜索条件中的值表达式之一。通过这样做,您告诉数据库管理系统使用该列中每一行的值作为该行的搜索条件迭代的值表达式。
以下查询的WHERE
子句对每一行应用更排他的搜索条件。它将从值等于 的任何行返回name
和max_bags
值:max_bags
4
- 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)
您还可以评估搜索条件谓词中的字符串值。以下查询返回其值等于的每一行的vol_id
和name
值:name
'Wanda'
- 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 都支持所有谓词。以下是五种最常用的搜索条件谓词类型及其使用的运算符:
比较:比较谓词将一个值表达式与另一个值表达式进行比较;在查询中,几乎总是这些值表达式中的至少一个是列的名称。六个比较运算符是:
=
: 测试两个值是否相等<>
: 测试两个值是否不等价<
: 测试第一个值是否小于第二个>
: 测试第一个值是否大于第二个<=
: 测试第一个值是否小于或等于第二个>=
: 测试第一个值是否大于或等于第二个
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
价值最高。您可以运行以下查询,该查询从表中返回name
和max_bags
值volunteers
:
- 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的其他教程。