小巧。快速。可靠。
三选二。
选择

1. 概述

select-stmt

WITH RECURSIVE common-table-expression , SELECT DISTINCT result-column , ALL FROM table-or-subquery join-clause , WHERE expr GROUP BY expr HAVING expr , WINDOW window-name AS window-defn , VALUES ( expr ) , , compound-operator select-core ORDER BY LIMIT expr ordering-term , OFFSET expr , expr

common-table-expression

compound-operator

expr

join-clause

ordering-term

result-column

table-or-subquery

window-defn

SELECT 语句用于查询数据库。SELECT 的结果是零行或多行数据,其中每行具有固定数量的列。SELECT 语句不会对数据库进行任何更改。

上面的“select-stmt”语法图试图在一个图中显示尽可能多的 SELECT 语句语法,因为一些读者发现这很有帮助。下面的“factored-select-stmt”是另一种语法图,它表达了相同的语法,但试图将语法分解成更小的块。

factored-select-stmt

请注意,在语法图中存在一些在实践中不允许的路径。一些例子

这些和其他类似的语法限制在文本中进行了描述。

SELECT 语句是 SQL 语言中最复杂的命令。为了使描述更容易理解,下面的一些段落将返回 SELECT 语句的数据的方式描述为一系列步骤。重要的是要记住,这纯粹是为了说明 - 在实践中,SQLite 或任何其他 SQL 引擎都不需要遵循此过程或任何其他特定过程。

2. 简单 SELECT 处理

SELECT 语句的核心是“简单 SELECT”,由下面的 select-coresimple-select-stmt 语法图所示。在实践中,大多数 SELECT 语句都是简单 SELECT 语句。

simple-select-stmt

WITH RECURSIVE common-table-expression , select-core ORDER BY LIMIT expr ordering-term , OFFSET expr , expr

common-table-expression

expr

ordering-term

select-core

SELECT DISTINCT result-column , ALL FROM table-or-subquery join-clause , WHERE expr GROUP BY expr HAVING expr , WINDOW window-name AS window-defn , VALUES ( expr ) , ,

join-clause

result-column

table-or-subquery

window-defn

生成简单 SELECT 语句的结果在下面的描述中表示为一个四步过程

  1. FROM 子句 处理:确定简单 SELECT 的输入数据。输入数据要么隐式地是一行 0 列(如果没有 FROM 子句),要么由 FROM 子句确定。

  2. WHERE 子句 处理:使用 WHERE 子句表达式过滤输入数据。

  3. GROUP BY、HAVING 和结果列表达式 处理:通过根据任何 GROUP BY 子句聚合数据并计算过滤后的输入数据集行的结果集表达式来计算结果行集。

  4. DISTINCT/ALL 关键字 处理:如果查询是“SELECT DISTINCT”查询,则从结果行集中删除重复行。

简单 SELECT 语句有两种类型:聚合查询和非聚合查询。如果简单 SELECT 语句包含 GROUP BY 子句或结果集中一个或多个聚合函数,则它是一个聚合查询。否则,如果简单 SELECT 不包含聚合函数或 GROUP BY 子句,则它是一个非聚合查询。

2.1. 输入数据的确定(FROM 子句处理)

简单 SELECT 查询使用的输入数据是一组N行,每行M列宽。

如果从简单 SELECT 语句中省略 FROM 子句,则输入数据隐式地为一行零列宽(即N=1 且M=0)。

如果指定了 FROM 子句,则简单 SELECT 查询操作的数据来自在 FROM 关键字后指定的表或子查询(括号中的 SELECT 语句)之一或多个。简单 SELECT 语句中 FROM 子句后 table-or-subquery 中指定的子查询的处理方式就像它是一个包含执行子查询语句返回的数据的表一样。子查询的每一列都具有子查询语句中相应表达式的排序规则亲和性

如果 FROM 子句中只有一个表或子查询,则 SELECT 语句使用的输入数据是命名表的表内容。如果 FROM 子句中有多个表或子查询,则所有表和/或子查询的内容将连接到一个数据集中,供简单 SELECT 语句操作。数据如何组合取决于用于将表或子查询连接在一起的特定 join-operatorjoin-constraint

所有 SQLite 中的连接都基于左右数据集的笛卡尔积。笛卡尔积数据集的列按顺序为左手数据集的所有列,然后是右手数据集的所有列。通过组合左手和右手数据集中的每一行唯一组合,笛卡尔积数据集中形成一行。换句话说,如果左手数据集由NleftMleft列组成,而右手数据集由NrightMright列组成,则笛卡尔积是一个Nleft×Nright行的数据集,每一行包含Mleft+Mright列。

如果 join-operator 是“CROSS JOIN”、“INNER JOIN”、“JOIN”或逗号(“,”),并且没有 ON 或 USING 子句,则连接的结果只是左手和右手数据集的笛卡尔积。如果 join-operator 确实有 ON 或 USING 子句,则根据以下要点处理它们

当多个表作为 FROM 子句的一部分连接在一起时,连接操作将按从左到右的顺序处理。换句话说,FROM 子句(A join-op-1 B join-op-2 C)计算为((A join-op-1 B)join-op-2 C)。

2.2. CROSS JOIN 的特殊处理。

“INNER JOIN”、“JOIN”和“,”连接运算符之间没有区别。它们在 SQLite 中完全可以互换。“CROSS JOIN”连接运算符产生的结果与“INNER JOIN”、“JOIN”和“,”运算符相同,但查询优化器处理方式不同,因为它阻止查询优化器重新排序连接中的表。应用程序程序员可以使用 CROSS JOIN 运算符直接影响用于实现 SELECT 语句的算法。除非在需要手动控制查询优化器的特定情况下,否则避免使用 CROSS JOIN。在应用程序开发的早期避免使用 CROSS JOIN,因为这样做是过早优化。CROSS JOIN 的特殊处理是 SQLite 特定的功能,而不是标准 SQL 的一部分。

2.3. WHERE 子句过滤。

如果指定了 WHERE 子句,则 WHERE 表达式将对输入数据中的每一行作为 布尔表达式 进行计算。只有 WHERE 子句表达式计算结果为真的行才会包含在数据集中,然后再继续。如果 WHERE 子句计算结果为假或 NULL,则将行从结果中排除。

对于 JOIN、INNER JOIN 或 CROSS JOIN,WHERE 子句中的约束表达式与 ON 子句中的约束表达式之间没有区别。但是,对于 LEFT、RIGHT 或 FULL OUTER JOIN,差异非常重要。在外连接中,对于另一个操作数上不匹配的行,额外的 NULL 行是在 ON 子句处理之后但 WHERE 子句处理之前添加的。因此,ON 子句中形如“left.x=right.y”的约束将允许所有 NULL 行通过。但是,如果相同的约束在 WHERE 子句中,则“right.y”或“left.x”中的 NULL 将阻止表达式“left.x=right.y”为真,从而将该行排除在输出之外。

2.4. 结果行集的生成

一旦来自 FROM 子句的输入数据(如果有)被 WHERE 子句表达式过滤,则计算简单 SELECT 的结果行集。具体如何执行取决于简单 SELECT 是聚合查询还是非聚合查询,以及是否指定了 GROUP BY 子句。

SELECT 和 FROM 关键字之间的表达式列表称为结果表达式列表。如果结果表达式是特殊表达式“*”,则将输入数据中的所有列替换为该表达式。如果表达式是 FROM 子句中表或子查询的别名后跟“.*”,则将命名表或子查询中的所有列替换为单个表达式。在结果表达式列表以外的任何上下文中使用“*”或“alias.*”表达式都是错误的。在没有 FROM 子句的简单 SELECT 查询中使用“*”或“alias.*”表达式也是错误的。

简单 SELECT 语句返回的行中的列数等于替换 * 和 alias.* 表达式后的结果表达式列表中的表达式数。每个结果行都是通过针对输入数据的单个行(或对于聚合查询,针对一组行)计算结果表达式列表中的表达式来计算的。

2.5. 聚合查询中的裸列

通常情况下,聚合查询中的所有列名要么是聚合函数的参数,要么出现在 GROUP BY 子句中。包含不在聚合函数内且未出现在 GROUP BY 子句(如果存在)中的列名的结果列称为“裸”列。示例

SELECT a, b, sum(c) FROM tab1 GROUP BY a;

在上面的查询中,“a”列是 GROUP BY 子句的一部分,因此输出的每一行都包含“a”的一个不同的值。“c”列包含在sum()聚合函数中,因此该输出列是具有相同“a”值的行中所有“c”值的总和。但是,裸列“b”的结果是什么?答案是“b”结果将是构成聚合的输入行之一中“b”的值。问题在于通常不知道使用哪个输入行来计算“b”,因此在许多情况下,“b”的值是未定义的。

当聚合函数为min()max()时,会发生特殊处理。示例

SELECT a, b, max(c) FROM tab1 GROUP BY a;

如果查询中正好有一个min()max()聚合,则结果集中所有裸列都取自也包含最小值或最大值的输入行。因此,在上面的查询中,输出中“b”列的值将是具有最大“c”值的输入行中“b”列的值。min()max()的这种特殊行为存在一些限制

  1. 如果相同最小值或最大值出现在两行或多行上,则可能会从这些行中的任何一行选择裸值。选择是任意的。无法预测将从哪一行选择裸值。对于同一查询内的不同裸列,选择可能不同。

  2. 如果查询中存在两个或多个min()max()聚合,则裸列值将取自其中一个聚合具有其最小值或最大值的行的其中一行。确定哪个min()max()聚合决定裸列值的选取是任意的。对于同一查询内的不同裸列,选择可能不同。

  3. 这种针对min()max()聚合的特殊处理仅适用于这些聚合的内置实现。如果应用程序使用应用程序定义的替代项覆盖内置min()max()聚合,则将从任意行中获取为裸列选择的值。

大多数其他 SQL 数据库引擎不允许使用裸列。如果在查询中包含裸列,其他数据库引擎通常会引发错误。能够在查询中包含裸列是 SQLite 特定的扩展。这被认为是一个特性,而不是一个错误。有关更多信息,请参阅SQLite 论坛主题 7481d2a6df8980ff中的讨论。

2.6. 删除重复行(DISTINCT 处理)

在简单 SELECT 语句中,SELECT 关键字后面可以跟随 ALL 或 DISTINCT 关键字之一。如果简单 SELECT 是 SELECT ALL,则 SELECT 返回结果行的整个集合。如果没有 ALL 或 DISTINCT,则行为就像指定了 ALL 一样。如果简单 SELECT 是 SELECT DISTINCT,则在返回结果行集之前,将从中删除重复行。为了检测重复行,两个 NULL 值被视为相等。应用常用规则来选择排序规则以比较文本值。

3. 复合 SELECT 语句

可以使用 UNION、UNION ALL、INTERSECT 或 EXCEPT 运算符将两个或多个简单 SELECT语句连接在一起以形成复合 SELECT,如下面的图表所示

复合-select-stmt

WITH RECURSIVE common-table-expression , select-core ORDER BY LIMIT expr UNION UNION ALL select-core INTERSECT EXCEPT ordering-term , OFFSET expr , expr

common-table-expression

expr

ordering-term

select-core

在复合 SELECT 中,所有组成 SELECT 必须返回相同数量的结果列。由于复合 SELECT 的组件必须是简单 SELECT 语句,因此它们不能包含ORDER BYLIMIT子句。ORDER BYLIMIT子句只能出现在整个复合 SELECT 的末尾,并且仅当复合的最后一个元素不是VALUES子句时。

使用 UNION ALL 运算符创建的复合 SELECT 返回 UNION ALL 运算符左侧 SELECT 的所有行,以及右侧 SELECT 的所有行。UNION 运算符的工作方式与 UNION ALL 相同,只是最终结果集中删除了重复行。INTERSECT 运算符返回左侧和右侧 SELECT 结果的交集。EXCEPT 运算符返回左侧 SELECT 返回的行子集,这些行不是右侧 SELECT 也返回的行。在返回结果集之前,将从 INTERSECT 和 EXCEPT 运算符的结果中删除重复行。

为了确定复合 SELECT 运算符结果的重复行,NULL 值被视为等于其他 NULL 值,并且不同于所有非 NULL 值。用于比较两个文本值的排序规则的确定方式,就像左侧和右侧 SELECT 语句的列是等于 (=) 运算符的左侧和右侧操作数一样,只是不为后缀 COLLATE 运算符指定的排序规则分配更高的优先级。在将行作为复合 SELECT 的一部分进行比较时,不会对任何值应用亲和性转换。

当三个或更多简单 SELECT 连接到复合 SELECT 中时,它们从左到右分组。换句话说,如果“A”、“B”和“C”都是简单 SELECT 语句,则 (A op B op C) 将被处理为 ((A op B) op C) 。

4. ORDER BY 子句

如果返回多行的 SELECT 语句没有 ORDER BY 子句,则返回行的顺序未定义。或者,如果 SELECT 语句确实具有 ORDER BY 子句,则附加到 ORDER BY 的表达式列表确定返回给用户的行的顺序。

复合 SELECT 语句中,只有最后一个或最右边的简单 SELECT 可以包含 ORDER BY 子句。该 ORDER BY 子句将应用于复合语句的所有元素。如果复合 SELECT 的最右边元素是VALUES 子句,则不允许在该语句上使用 ORDER BY 子句。

行首先根据 ORDER BY 列表中最左侧表达式的计算结果进行排序,然后通过计算第二左侧表达式来解决冲突,依此类推。对于所有 ORDER BY 表达式计算结果都相等的两个行,返回顺序未定义。每个 ORDER BY 表达式之后可以可选地跟 ASC(较小的值先返回)或 DESC(较大的值先返回)关键字之一。如果没有指定 ASC 或 DESC,则默认情况下按升序(较小的值优先)排序。

SQLite 将 NULL 值视为小于任何其他值用于排序目的。因此,NULL 值自然出现在 ASC 排序的开头和 DESC 排序的结尾。这可以通过使用“ASC NULLS LAST”或“DESC NULLS FIRST”语法来更改。

每个 ORDER BY 表达式按如下方式处理

  1. 如果 ORDER BY 表达式是常量整数 K,则该表达式被视为结果集第 K 列的别名(列从左到右编号,从 1 开始)。

  2. 如果 ORDER BY 表达式是与一个输出列的别名相对应的标识符,则该表达式被视为该列的别名。

  3. 否则,如果 ORDER BY 表达式是任何其他表达式,则对其进行计算并使用返回的值来对输出行进行排序。如果 SELECT 语句是简单 SELECT,则 ORDER BY 可以包含任何任意表达式。但是,如果 SELECT 是复合 SELECT,则不是输出列别名的 ORDER BY 表达式必须与用作输出列的表达式完全相同。

为了对行进行排序,值的比较方式与比较表达式相同。用于比较两个文本值的排序规则如下

  1. 如果 ORDER BY 表达式使用后缀COLLATE 运算符分配了排序规则,则使用指定的排序规则。

  2. 否则,如果 ORDER BY 表达式是已使用后缀COLLATE 运算符分配了排序规则的表达式的别名,则使用分配给该别名表达式的排序规则。

  3. 否则,如果 ORDER BY 表达式是列或表达式的别名,而该表达式是列,则使用该列的默认排序规则。

  4. 否则,使用BINARY排序规则。

复合 SELECT 语句中,所有 ORDER BY 表达式都作为复合语句结果列之一的别名处理。如果 ORDER BY 表达式不是整数别名,则 SQLite 会在复合语句中最左侧的 SELECT 中搜索与上面第二或第三条规则匹配的结果列。如果找到匹配项,则搜索停止,并且该表达式被视为与之匹配的结果列的别名。否则,尝试下一个右侧的 SELECT,依此类推。如果在任何组成 SELECT 的结果列中都找不到匹配的表达式,则为错误。ORDER BY 子句的每个术语都是单独处理的,并且可以与复合语句中不同 SELECT 语句的结果列匹配。

5. LIMIT 子句

LIMIT 子句用于对整个 SELECT 语句返回的行数设置上限。

复合 SELECT中,只有最后一个或最右边的简单 SELECT可以包含 LIMIT 子句。在复合 SELECT中,LIMIT 子句应用于整个复合语句,而不仅仅是最后的 SELECT。如果最右边的简单 SELECTVALUES 子句,则不允许使用 LIMIT 子句。

只要 LIMIT 子句中的任何标量表达式计算结果为整数或可以无损转换为整数的值,就可以使用它。如果表达式的计算结果为 NULL 值或任何其他无法无损转换为整数的值,则返回错误。如果 LIMIT 表达式计算结果为负值,则返回的行数没有上限。否则,SELECT 仅返回其结果集的前 N 行,其中 N 是 LIMIT 表达式计算结果的值。或者,如果 SELECT 语句在没有 LIMIT 子句的情况下返回的行数少于 N 行,则返回整个结果集。

附加到可选 OFFSET 子句(可能位于 LIMIT 子句之后)的表达式也必须计算结果为整数,或可以无损转换为整数的值。如果表达式具有 OFFSET 子句,则从 SELECT 语句返回的结果集中省略前 M 行,并返回接下来的 N 行,其中 M 和 N 分别是 OFFSET 和 LIMIT 子句计算结果的值。或者,如果 SELECT 在没有 LIMIT 子句的情况下返回的行数少于 M+N 行,则跳过前 M 行,并返回剩余的行(如果有)。如果 OFFSET 子句计算结果为负值,则结果与计算结果为零相同。

LIMIT 子句可以使用两个用逗号分隔的标量表达式,而不是单独的 OFFSET 子句。在这种情况下,第一个表达式用作 OFFSET 表达式,第二个用作 LIMIT 表达式。这与直觉相反,因为当使用 OFFSET 子句时,两个表达式中的第二个是 OFFSET,第一个是 LIMIT。这种 OFFSET 和 LIMIT 的反转是有意的——它最大程度地与其他 SQL 数据库系统兼容。但是,为了避免混淆,强烈建议程序员使用使用“OFFSET”关键字的 LIMIT 子句的形式,并避免使用带有逗号分隔的偏移量的 LIMIT 子句。

6. VALUES 子句

短语“VALUES(expr-list)”与“SELECT expr-list”含义相同。短语“VALUES(expr-list-1),...,(expr-list-N)”与“SELECT expr-list-1 UNION ALL ... UNION ALL SELECT expr-list-N”含义相同。这两种形式是相同的,只是复合语句中 SELECT 语句的数量受SQLITE_LIMIT_COMPOUND_SELECT限制,而 VALUES 子句中的行数没有任意限制。

VALUES 子句的使用有一些限制,在语法图中没有显示

7. WITH 子句

SELECT 语句前面可以可选地加上一个WITH 子句,该子句定义一个或多个公用表表达式供 SELECT 语句中使用。

8. FROM 子句中的表值函数

包含隐藏列虚拟表可以在 FROM 子句中像表值函数一样使用。表值函数的参数成为虚拟表隐藏列上的约束。更多信息可以在虚拟表文档中找到。

9. 与标准 SQL 的偏差

SQLite 的 SELECT 语法与标准 SQL 略有不同。这些差异是由于以下几个原因造成的

无论输入怪癖的来源是什么,我们通常都避免尝试“修复”它们,因为对输入语法的任何新限制都可能导致至少数百万使用 SQLite 的应用程序出现故障。我们不希望这样。SQLite 开发团队的目标是最大程度地保持向后兼容性。因此,如果语法怪癖无害,我们就会保留它并在本文档中记录它,而不是尝试修复它。

9.1. 奇怪的 JOIN 名称

SQLite 接受所有常用的 JOIN 运算符语法

join-operator

NATURAL LEFT OUTER JOIN , RIGHT FULL INNER CROSS

但它并没有就此止步。SQLite 在指定连接运算符的方式上实际上非常灵活。通用语法为

blah blah blah JOIN

其中“blah”有 1 到 3 个实例,每个实例可以是“CROSS”、“FULL”、“INNER”、“LEFT”、“NATURAL”、“OUTER”或“RIGHT”中的任何一个。SQLite 解析器将这些关键字中的每一个都视为连接的属性,这些属性可以以任何顺序组合。这创建了在语法图中指定的连接类型之外的许多新的和创造性的连接类型的可能性。其中一些非标准连接类型被明确禁止。例如,您不能说“INNER OUTER JOIN”,因为这将是矛盾的。但是您可以说“OUTER LEFT NATURAL JOIN”,这与“NATURAL LEFT OUTER JOIN”含义相同。或者您可以说“LEFT RIGHT JOIN”,这与“FULL JOIN”相同。

请记住:您可以使用这些非标准连接类型,但您不应该。坚持使用标准的 JOIN 语法,以便与其他 SQL 数据库引擎保持可移植性。

9.2. 灵活的连接语法

标准 SQL 对连接语法的限制比 SQLite 严格。在标准 SQL 中,除逗号连接、CROSS JOIN 和 NATURAL 连接之外的所有连接都必须具有 ON 子句或 USING 子句,而逗号连接、CROSS JOIN 和 NATURAL 连接不能同时具有 ON 或 USING 子句。SQLite 对连接语法并不那么挑剔。SQLite 将接受并处理逗号连接或 CROSS JOIN 上的 ON 或 USING 子句,并且允许您从任何连接中省略 ON 或 USING 子句。在 SQLite 中,唯一的限制是

SQLite 甚至允许您从外部连接中省略 ON 或 USING 子句,尽管这样做意味着外部连接不受约束(就像 ON 子句为“ON true”一样),这使得外部连接的行为类似于内部连接。

9.3. 逗号连接和 CROSS JOIN 的优先级

在标准 SQL 中,使用 JOIN 关键字的连接优先于逗号连接。也就是说,JOIN 运算符先于逗号运算符发生。在 SQLite 中并非如此,所有连接的优先级都相同。

考虑以下示例

... FROM t1, t2 NATURAL FULL JOIN t3 ...

在标准 SQL 中,t2 和 t3 之间的 FULL JOIN 将首先发生,然后左连接的结果将与 t1 进行交叉连接。但是 SQLite 始终从左到右处理所有连接。因此,SQLite 将首先对 t1 和 t2 进行交叉连接,然后将该交叉连接的结果馈送到与 t3 的 FULL JOIN 中。内部连接本质上是关联的,因此只有当您的 FROM 子句包含一个或多个外部连接时,差异才会显现。

您可以解决此问题,并使您的 SQL 语句在所有系统上都可移植,方法是遵守以下样式规则

任何一条建议都足以避免问题,并且大多数程序员会本能地遵循所有这些建议而无需被告知,因此逗号连接和 JOIN 关键字在 SQLite 中缺乏优先级差异在实践中很少出现。但是,您应该意识到这个问题,以防它出现。

此页面最后修改于 2024-05-11 12:18:17 UTC