结构化查询语言(Structured Query Language)
SELECT 命令
SELECT
命令从一个或多个表或视图中检索数据。它通常由以下语言元素组成:
SELECT <things_to_be_displayed> -- 所谓的“投影” - 通常是列名列表
FROM <tablename> -- 表或视图的名称及其别名
WHERE <where_clause> -- 所谓的“限制”或“搜索条件”
GROUP BY <group_by_clause>
HAVING <having_clause>
ORDER BY <order_by_clause>
OFFSET <offset_clause>
FETCH <fetch_first_or_next_clause>;
除了前两个元素外,其他所有元素都是可选的。语言元素的顺序是强制性的。在命令的某些位置,可能会以递归方式开始新的 SELECT
命令。
投影(指定结果列)
在 SELECT
命令的投影部分,您可以指定列的列表、对列进行操作、函数、固定值或新的 SELECT
命令。
-- SQL 命令中可以使用 C/Java 风格的注释
SELECT id, /* 列名 */
concat(firstname, lastname), /* concat() 函数 */
weight + 5, /* 加法运算 */
'kg' /* 一个值 */
FROM person;
DBMS 将检索十行,每行由四列组成。
我们可以以任何顺序混合列的顺序,或者多次检索它们。
SELECT id, lastname, lastname, 'weighs', weight, 'kg'
FROM person;
星号 *
是所有列列表的缩写。
SELECT * FROM person;
对于数值列,我们可以应用通常的数值运算符 +
、-
、*
和 /
。根据数据类型,还有许多预定义的函数:幂、平方根、取模、字符串函数、日期函数等。
使用关键字 DISTINCT 实现唯一性
可以使用关键字 DISTINCT
来压缩结果,以确保唯一值。在这种情况下,所有结果行中如果有重复的行,将被压缩为一行。换句话说:重复项被消除——就像集合理论中一样。
-- 检索十行
SELECT lastname
FROM person;
-- 仅检索七行。重复值被舍弃。
SELECT DISTINCT lastname
FROM person;
提示:
- 关键字
DISTINCT
指的是结果行的整体,您可以将其想象为所有列的连接。它紧跟在SELECT
关键字之后。 - 以下查询将导致十行结果,尽管有三个人具有相同的
lastname
。
SELECT DISTINCT lastname, firstname
FROM person;
- 再次仅检索七行。
SELECT DISTINCT lastname, lastname
FROM person;
列名的别名
有时我们希望为结果列赋予更具描述性的名称。我们可以通过在投影中选择别名来实现。这些别名将在结果集中作为新名称显示。图形用户界面(GUIs)将显示别名作为列标签。
-- 关键字 'AS' 是可选的
SELECT lastname AS family_name, weight AS weight_in_kg
FROM person;
函数
在投影(以及某些其他位置)中有预定义的函数。最常用的包括:
count(<columnname>|'*')
:计数结果行的数量。max(<columnname>)
:结果集中<column>
的最高值。也适用于字符串。min(<columnname>)
:结果集中<column>
的最低值。也适用于字符串。sum(<columnname>)
:数值列所有值的总和。avg(<columnname>)
:数值列的平均值。concat(<columnname_1>, <columnname_2>)
:连接两个列。或者可以使用||
运算符表示:<columnname_1> || <columnname_2>
标准 SQL 和每个 DBMS 提供了许多其他函数。
我们必须区分那些每行返回一个值的函数,如 concat()
,以及那些每个完整结果集仅返回一行的函数,如 max()
。前者可以与列名以任何组合方式混合,如本页最初的示例所示。对于后者,存在一个问题:如果我们将它们与常规列名混合,DBMS 会在查询中识别出矛盾。一方面,它应该精确地检索一个值(一行),另一方面,它应该检索许多值(多行)。DBMS 的反应因供应商而异。有些会在运行时抛出错误消息——符合 SQL 标准——其他的则可能返回可疑的结果。
-- 正常工作
SELECT lastname, concat(weight, ' kg')
FROM person;
-- 检查您的 DBMS 的反应。它应该抛出错误消息。
SELECT lastname, avg(weight)
FROM person;
-- 合法的函数组合,结果是一行四列
SELECT min(weight), max(weight), avg(weight) as average_1, sum(weight) / count(*) as average_2
FROM person;
您可以根据需要复制/粘贴这些示例,以帮助您学习和实践 SQL。
SELECT 中的 SELECT
如果我们真的想在结合多个列的情况下查看面向结果集的函数的结果,我们可以在一个简单情况下出现列名的位置开始一个全新的 SELECT
。这个第二个 SELECT
是一个完全独立的命令。请注意:它将在第一个 SELECT
的每一个结果行上执行!
-- 检索 10 行;注意额外的括号以区分两个 SELECT
SELECT lastname, (SELECT avg(weight) FROM person)
FROM person;
-- 计算每个人的体重相对于所有人的平均体重的百分比
SELECT lastname,
weight,
weight * 100 / (SELECT avg(weight) FROM person) AS percentage_of_average
FROM person;
表名
关键字 FROM
用于指定命令将作用的表。这个表名可以作为标识符使用。在最初的简单示例中,可以使用表名标识符为列名添加前缀,但这不是必需的。在后来的更复杂命令中,表名标识符是必需的特性。
SELECT person.firstname, person.lastname
FROM person;
- 为表名定义别名(类似于列名)。为了保持清晰,我们通常用名称的第一个字符来缩写表名。
SELECT p.firstname, p.lastname
FROM person AS p; -- 提示:并非所有系统都接受带有表别名的关键字 'AS'。在这些情况下请省略它!
- 关键字
AS
同样是可选的。
SELECT p.firstname, p.lastname
FROM person p;
限制条件(指定结果行)
在 WHERE
子句中,我们指定一些“搜索条件”,这些条件位于命名的表或视图中。评估这些条件通常是执行 SELECT
命令时的第一步。在任何行可以被排序或显示之前,它必须满足子句中的条件。
如果省略该子句,表中的所有行都会被检索。否则,行数将根据指定的条件被减少。例如,如果我们指定 weight <
70
,则只检索那些 weight
列存储值小于 70 的行。限制条件通过评估列值(有时是评估行的存在性等其他因素,但目前我们专注于基本原则)作用于表的行。因此,我们可以想象 WHERE
子句的评估会产生一个行列表。这个行列表将在后续步骤中被处理,如排序、分组或显示特定列(投影)。
比较操作
我们以与在不同编程语言中相同的方式比较变量、常量值和函数调用的结果。唯一的区别是我们使用列名而不是变量。比较运算符必须与它们要操作的数据类型匹配。比较的结果是一个布尔值。如果为 true
,则相应的行将进一步处理。一些示例:
'weight = 70'
比较列weight
是否等于常量值70
。'70 = weight'
:与之前相同。'firstname = lastname'
比较同一行中的两个列是否相等。像'Frederic Frederic'
这样的名字会评估为true
。'firstname < lastname'
根据字符串的字典顺序公平地比较两个列。'LENGTH(firstname) < 5'
将函数调用的结果与常量值5
进行比较。函数LENGTH()
作用于字符串并返回一个数字。
布尔逻辑
通常,我们希望指定不止一个搜索条件,例如,是否有在旧金山出生且姓氏为 Baker 的人?为此,我们独立指定每一个必要的比较,并使用布尔运算符 AND
或 OR
将它们连接起来。
SELECT *
FROM person
WHERE place_of_birth = 'San Francisco'
AND lastname = 'Baker';
比较的结果是布尔值。它可以通过一元运算符 NOT
在 true
和 false
之间切换。
SELECT *
FROM person
WHERE place_of_birth = 'San Francisco'
AND NOT lastname = 'Baker'; -- 除了 'Baker' 的所有人
提示:
- 前一个示例中的
NOT
是对比较结果的“一元操作”。它不是对AND
的补充。
SELECT *
FROM person
WHERE place_of_birth = 'San Francisco'
AND (NOT (lastname = 'Baker')); -- 与之前相同,但使用括号明确表示
比较和布尔逻辑的优先级如下:
- 所有比较
NOT
运算符AND
运算符OR
运算符
-- AND(在 SF 出生且姓氏为 Baker;作为中间结果有 1 个匹配)
-- 将在处理 OR(Yorgos)之前被处理
-- 1 + 1 ==> 2 行
SELECT *
FROM person
WHERE place_of_birth = 'San Francisco' -- 4 个匹配 SF
AND lastname = 'Baker' -- 1 个匹配 Baker
OR firstname = 'Yorgos' -- 1 个匹配 Yorgos
;
-- 使用括号明确表示优先级。
-- AND 在 OR 之前被处理。
-- 结果 ==> 与上述相同的 2 行
SELECT *
FROM person
WHERE (place_of_birth = 'San Francisco' -- 4 个匹配 SF
AND lastname = 'Baker') -- 1 个匹配 Baker
OR firstname = 'Yorgos' -- 1 个匹配 Yorgos
;
-- AND(Yorgos Baker;作为中间结果没有匹配)将比 OR(在 SF 出生;4 个匹配)先被处理
-- 0 + 4 ==> 4 行
SELECT *
FROM person
WHERE place_of_birth = 'San Francisco' -- 4 个匹配 SF
OR firstname = 'Yorgos' -- 1 个匹配 Yorgos
AND lastname = 'Baker' -- 1 个匹配 Baker
;
-- 使用括号明确表示优先级。
-- AND 在 OR 之前被处理。
-- 结果 ==> 与上述相同的 4 行
SELECT *
FROM person
WHERE place_of_birth = 'San Francisco' -- 4 个匹配 SF
OR (firstname = 'Yorgos' -- 1 个匹配 Yorgos
AND lastname = 'Baker') -- 1 个匹配 Baker
;
- 我们可以通过指定括号来修改运算顺序。
- 与第一个示例相同,添加括号后,只有一行匹配。
SELECT *
FROM person
WHERE place_of_birth = 'San Francisco' -- 4 个匹配 SF
AND (lastname = 'Baker' -- 1 个匹配 Baker
OR firstname = 'Yorgos') -- 1 个匹配 Yorgos
;
两种缩写
(注:用户在此部分只提供了标题“Two abbreviations”,未提供具体内容。若有后续内容,请补充。)
您可以根据需要复制/粘贴这些示例,以帮助您学习和实践 SQL。
SELECT 中的 SELECT
如果我们真的想在结合多个列的情况下查看面向结果集的函数的结果,我们可以在一个简单情况下出现列名的位置开始一个全新的 SELECT
。这个第二个 SELECT
是一个完全独立的命令。请注意:它将在第一个 SELECT
的每一个结果行上执行!
-- 检索 10 行;注意额外的括号以区分两个 SELECT
SELECT lastname, (SELECT avg(weight) FROM person)
FROM person;
-- 计算每个人的体重相对于所有人的平均体重的百分比
SELECT lastname,
weight,
weight * 100 / (SELECT avg(weight) FROM person) AS percentage_of_average
FROM person;
表名
关键字 FROM
用于指定命令将作用的表。这个表名可以作为标识符使用。在最初的简单示例中,可以使用表名标识符为列名添加前缀,但这不是必需的。在后来的更复杂命令中,表名标识符是必需的特性。
SELECT person.firstname, person.lastname
FROM person;
- 为表名定义别名(类似于列名)。为了保持清晰,我们通常用名称的第一个字符来缩写表名。
SELECT p.firstname, p.lastname
FROM person AS p; -- 提示:并非所有系统都接受带有表别名的关键字 'AS'。在这些情况下请省略它!
- 关键字
AS
同样是可选的。
SELECT p.firstname, p.lastname
FROM person p;
限制条件(指定结果行)
在 WHERE
子句中,我们指定一些“搜索条件”,这些条件位于命名的表或视图中。评估这些条件通常是执行 SELECT
命令时的第一步。在任何行可以被排序或显示之前,它必须满足子句中的条件。
如果省略该子句,表中的所有行都会被检索。否则,行数将根据指定的条件被减少。例如,如果我们指定 weight <
70
,则只检索那些 weight
列存储值小于 70 的行。限制条件通过评估列值(有时是评估行的存在性等其他因素,但目前我们专注于基本原则)作用于表的行。因此,我们可以想象 WHERE
子句的评估会产生一个行列表。这个行列表将在后续步骤中被处理,如排序、分组或显示特定列(投影)。
比较操作
我们以与在不同编程语言中相同的方式比较变量、常量值和函数调用的结果。唯一的区别是我们使用列名而不是变量。比较运算符必须与它们要操作的数据类型匹配。比较的结果是一个布尔值。如果为 true
,则相应的行将进一步处理。一些示例:
'weight = 70'
比较列weight
是否等于常量值70
。'70 = weight'
:与之前相同。'firstname = lastname'
比较同一行中的两个列是否相等。像'Frederic Frederic'
这样的名字会评估为true
。'firstname < lastname'
根据字符串的字典顺序公平地比较两个列。'LENGTH(firstname) < 5'
将函数调用的结果与常量值5
进行比较。函数LENGTH()
作用于字符串并返回一个数字。
布尔逻辑
通常,我们希望指定不止一个搜索条件,例如,是否有在旧金山出生且姓氏为 Baker 的人?为此,我们独立指定每一个必要的比较,并使用布尔运算符 AND
或 OR
将它们连接起来。
SELECT *
FROM person
WHERE place_of_birth = 'San Francisco'
AND lastname = 'Baker';
比较的结果是布尔值。它可以通过一元运算符 NOT
在 true
和 false
之间切换。
SELECT *
FROM person
WHERE place_of_birth = 'San Francisco'
AND NOT lastname = 'Baker'; -- 除了 'Baker' 的所有人
提示:
- 前一个示例中的
NOT
是对比较结果的“一元操作”。它不是对AND
的补充。
SELECT *
FROM person
WHERE place_of_birth = 'San Francisco'
AND (NOT (lastname = 'Baker')); -- 与之前相同,但使用括号明确表示
比较和布尔逻辑的优先级如下:
- 所有比较
NOT
运算符AND
运算符OR
运算符
-- AND(在 SF 出生且姓氏为 Baker;作为中间结果有 1 个匹配)
-- 将在处理 OR(Yorgos)之前被处理
-- 1 + 1 ==> 2 行
SELECT *
FROM person
WHERE place_of_birth = 'San Francisco' -- 4 个匹配 SF
AND lastname = 'Baker' -- 1 个匹配 Baker
OR firstname = 'Yorgos' -- 1 个匹配 Yorgos
;
-- 使用括号明确表示优先级。
-- AND 在 OR 之前被处理。
-- 结果 ==> 与上述相同的 2 行
SELECT *
FROM person
WHERE (place_of_birth = 'San Francisco' -- 4 个匹配 SF
AND lastname = 'Baker') -- 1 个匹配 Baker
OR firstname = 'Yorgos' -- 1 个匹配 Yorgos
;
-- AND(Yorgos Baker;作为中间结果没有匹配)将比 OR(在 SF 出生;4 个匹配)先被处理
-- 0 + 4 ==> 4 行
SELECT *
FROM person
WHERE place_of_birth = 'San Francisco' -- 4 个匹配 SF
OR firstname = 'Yorgos' -- 1 个匹配 Yorgos
AND lastname = 'Baker' -- 1 个匹配 Baker
;
-- 使用括号明确表示优先级。
-- AND 在 OR 之前被处理。
-- 结果 ==> 与上述相同的 4 行
SELECT *
FROM person
WHERE place_of_birth = 'San Francisco' -- 4 个匹配 SF
OR (firstname = 'Yorgos' -- 1 个匹配 Yorgos
AND lastname = 'Baker') -- 1 个匹配 Baker
;
- 我们可以通过指定括号来修改运算顺序。
- 与第一个示例相同,添加括号后,只有一行匹配。
SELECT *
FROM person
WHERE place_of_birth = 'San Francisco' -- 4 个匹配 SF
AND (lastname = 'Baker' -- 1 个匹配 Baker
OR firstname = 'Yorgos') -- 1 个匹配 Yorgos
;
两种缩写
有时我们会通过使用 BETWEEN
关键字来简化语法。它定义了一个下限和上限,主要用于数值和日期值,但也适用于字符串。
SELECT *
FROM person
WHERE weight >= 70
AND weight <= 90;
-- 等效的更简短、更具表达性的写法
SELECT *
FROM person
WHERE weight BETWEEN 70 AND 90; -- BETWEEN 包含两个边界值
对于将列或函数与多个值进行比较,我们可以使用简短的 IN
表达式。
SELECT *
FROM person
WHERE lastname = 'de Winter'
OR lastname = 'Baker';
-- 等效的更简短、更具表达性的写法
SELECT *
FROM person
WHERE lastname IN ('de Winter', 'Baker');
FETCH:选择特定行
有时我们对所有结果行不感兴趣,例如:我们可能只想查看前 3 行或 10 行。这可以通过 OFFSET
和 FETCH
子句来实现。OFFSET
指定要跳过的行数(从结果集的开头开始计算),FETCH
指定在此之后要停止返回的行数。
SELECT *
FROM person
WHERE place_of_birth = 'San Francisco'
ORDER BY firstname
FETCH FIRST 2 ROWS ONLY -- 仅前 2 行
;
SELECT *
FROM person
ORDER BY id -- WHERE 子句(和 ORDER BY 子句)是可选的
OFFSET 5 ROWS
FETCH FIRST 2 ROWS ONLY -- 仅第 6 和第 7 行(根据 ORDER BY)
;
请注意,OFFSET
和 FETCH
子句是 SELECT
命令的独立部分。一些实现将此功能作为 WHERE
子句的一部分或使用不同的关键字(如 ROWNUM
、START
、SKIP
、LIMIT
)来处理。
OFFSET
和 FETCH
的功能也可以通过窗口函数及其更通用的语法来实现。
分组(Grouping)
我们将在后面的章节中介绍 GROUP BY
子句与 HAVING
子句的结合使用。
排序(Sorting)
DBMS 可以自由地以任意顺序返回结果行。行可以按主键顺序、它们存储到数据库中的时间顺序、内部以 B 树组织的键的顺序,甚至是随机顺序返回。关于返回行的顺序,DBMS 可以按自己的意愿进行处理。不要期待任何特定的顺序。
如果我们期望特定的行顺序,必须明确表达我们的意愿。我们可以在 ORDER BY
子句中做到这一点。在那里,我们指定一列名列表,并结合升序或降序排序选项。
-- 所有人员按体重的升序(默认)顺序排序
SELECT *
FROM person
ORDER BY weight;
-- 所有人员按体重的降序排序
SELECT *
FROM person
ORDER BY weight DESC;
在上述结果中,有两行的 weight
列的值相同。由于这种情况会导致随机结果,我们可以指定更多的列。接下来的列仅在所有前面的列值相同时才会被处理。
-- 所有人员按体重的降序排序。在模糊情况下,按出生地的升序排序:Birmingham 在 San Francisco 之前。
SELECT *
FROM person
ORDER BY weight DESC, place_of_birth;
在 ORDER BY
子句中,我们可以指定任何处理表的列。我们不局限于投影返回的列。
-- 与上述排序相同
SELECT firstname, lastname
FROM person
ORDER BY weight DESC, place_of_birth;
组合语言元素
SELECT
命令的前两个元素是必需的:从 SELECT
到第一个表(或视图)名称的部分。其他所有元素都是可选的。如果我们也指定了可选的元素,必须牢记它们的预定顺序。但它们可以根据我们的需求进行组合。
-- 我们已经在本页看到:SELECT / FROM / WHERE / ORDER BY
SELECT p.lastname,
p.weight,
p.weight * 100 / (SELECT avg(p2.weight) FROM person p2) AS percentage_of_average
FROM person p
WHERE p.weight BETWEEN 70 AND 90
ORDER BY p.weight DESC, p.place_of_birth;
进一步信息
关于 SELECT
命令的其他选项,还有更多信息。
- 连接操作(Join Operation)
- 分组(Grouping)
IS NULL
谓词- 预定义函数(Predefined Functions)
- 集合操作(Set Operations)
- CASE 表达式(Case Expression)
- LIKE 谓词
- 子查询(Subquery)
您可以根据需要复制/粘贴这些示例,以帮助您学习和实践 SQL。