表表达式 计算一张表。表表达式包含一个 FROM
子句,其后面可以跟有 WHERE
、GROUP BY
和 HAVING
子句。普通表表达式只是引用磁盘上的表,所谓基表,但更复杂的表达式可用于通过各种方式修改或组合基表。
表表达式中的可选 WHERE
、GROUP BY
和 HAVING
子句指定在 FROM
子句中派生的表上执行的连续转换管道。所有这些转换都产生一张虚拟表,该虚拟表提供传递到所选列表以计算查询的输出行的行。
FROM
子句 #FROM
子句从逗号分隔的表引用列表中给出的一个或多个其他表派生一张表。
FROMtable_reference
[,table_reference
[, ...]]
表引用可以是表名称(可能具有模式限定)或派生表,例如子查询、JOIN
构造或这些的复杂组合。如果在 FROM
子句中列出了多于一个表引用,则对这些表进行交叉连接(即生成它们行的笛卡尔积;请参阅下文)。FROM
列表的结果是中间虚拟表,然后可由 WHERE
、GROUP BY
和 HAVING
子句执行转换,并最终是整体表表达式的结果。
当表引用指定一个作为表继承层次结构的父级的表时,表引用不仅生成该表中的行,还生成其所有后代表中的所有行,除非关键字 ONLY
出现在表名前面。但是,引用仅生成在已命名表中出现的列 — 将忽略在子表中添加的任何列。
除了在表名之前填写 ONLY
外,也可以在表名之后填写 *
来明确指出后代表也是包含项。由于现在默认情况下是搜索后代表,因此没有理由再使用此语法。但是,为了与旧版本兼容,该语法仍然得到支持。
连接表是按照特殊连接类型的规则,根据另外两张(真实表或派生表)得出的表。提供内部连接、外部连接和交叉连接。连接表的通用语法为:
T1
join_type
T2
[join_condition
]
所有类型的连接都可以连接在一起或嵌套:T1
和 T2
可以是连接表,也可以同时是连接表。可以在 JOIN
从句周围使用括号来控制连接顺序。如果没有括号,JOIN
从句将从左到右嵌套。
连接类型
T1
CROSS JOINT2
对于 T1
和 T2
中的每一种可能的行组合(即笛卡尔积),连接表都将包含一行,该行由 T1
中的所有列组成,后跟 T2
中的所有列。如果这两张表分别有 N 和 M 行,则连接表将有 N * M 行。
FROM
等同于 T1
CROSS JOIN T2
FROM
(见下文)。它还等同于 T1
INNER JOIN T2
ON TRUEFROM
。T1
, T2
当同时出现两张以上的表时,后一种等同关系不完全成立,因为 JOIN
的结合比逗号紧密。例如,FROM
与 T1
CROSS JOIN T2
INNER JOIN T3
ON condition
FROM
不同,因为T1
, T2
INNER JOIN T3
ON condition
condition
在第一种情况下可以引用 T1
,而在第二种情况下不可以。
T1
{ [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOINT2
ONboolean_expression
T1
{ [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOINT2
USING (join column list
)T1
NATURAL { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOINT2
在所有形式中,单词 INNER
和 OUTER
为可选。 INNER
为默认值; LEFT
、RIGHT
和 FULL
表示外部连接。
连接条件 在 ON
或 USING
子句中指定,或由单词 NATURAL
暗示。连接条件确定这两张源表中的哪几行被认为“”匹配“”,详细说明如下。
合格连接的可能类型有
INNER JOIN
对于 T1 的每一行 R1,连接的表都会有一行,用于满足与 R1 的连接条件的 T2 中的每一行。
LEFT OUTER JOIN
首先,执行内部连接。然后,对于 T1 中不满足 T2 中任意行连接条件的每一行,都会使用 T2 列中的空值添加一行连接的行。因此,连接表总是会至少有一行 T1 的每一行。
RIGHT OUTER JOIN
首先,执行内部连接。然后,对于 T2 中不满足 T1 中任意行连接条件的每一行,都会使用 T1 列中的空值添加一行连接的行。这是左连接的逆:结果表总是会有一行 T2 的每一行。
FULL OUTER JOIN
首先,执行内部连接。然后,对于 T1 中不满足 T2 中任意行连接条件的每一行,都会使用 T2 列中的空值添加一行连接的行。同样,对于 T2 中不满足 T1 中任意行连接条件的每一行,都会使用 T1 列中的空值添加一行连接的行。
ON
子句是最通用的连接条件:它采用与 WHERE
子句中相同的布尔值表达式。如果 ON
表达式的结果为真,则表示 T1
和 T2
中的一对行匹配。
USING
子句是一种简写,它允许在连接的两侧都对连接列使用相同名称的特定情况下进行利用。它获取共享列名称的用逗号分隔的列表,并构成一个连接条件,其中包括对每个列的等式比较。例如,使用 USING (a, b)
连接 T1
和 T2
会生成连接条件 ON
。T1
.a = T2
.a AND T1
.b = T2
.b
此外,JOIN USING
的输出会隐藏多余的列:无需打印两个匹配的列,因为它们必须具有相等的值。虽然 JOIN ON
会生成来自 T1
的所有列,后跟来自 T2
的所有列,但 JOIN USING
为列出的每个列对(按所列顺序)生成一个输出列,后跟来自 T1
的任何剩余列,后跟来自 T2
的任何剩余列。
最后,NATURAL
是 USING
的一种简写形式:它构成包含出现在两个输入表中的所有列名称的 USING
列表。与 USING
一样,这些列只会出现在输出表中一次。如果不存在公共列名称,则 NATURAL JOIN
的行为类似于 CROSS JOIN
。
由于只组合所列的列,因此 USING
在连接关系中不会像列更改那样出现意外。NATURAL
的风险会高得多,因为对任一关系进行任何会添加匹配新列名称的架构更改都会导致连接也组合该新列。
将此整合起来,假设我们有表 t1
num | name -----+------ 1 | a 2 | b 3 | c
和 t2
num | value -----+------- 1 | xxx 3 | yyy 5 | zzz
那么我们会得到各个连接的以下结果
=>
SELECT * FROM t1 CROSS JOIN t2;
num | name | num | value -----+------+-----+------- 1 | a | 1 | xxx 1 | a | 3 | yyy 1 | a | 5 | zzz 2 | b | 1 | xxx 2 | b | 3 | yyy 2 | b | 5 | zzz 3 | c | 1 | xxx 3 | c | 3 | yyy 3 | c | 5 | zzz (9 rows)=>
SELECT * FROM t1 INNER JOIN t2 ON t1.num = t2.num;
num | name | num | value -----+------+-----+------- 1 | a | 1 | xxx 3 | c | 3 | yyy (2 rows)=>
SELECT * FROM t1 INNER JOIN t2 USING (num);
num | name | value -----+------+------- 1 | a | xxx 3 | c | yyy (2 rows)=>
SELECT * FROM t1 NATURAL INNER JOIN t2;
num | name | value -----+------+------- 1 | a | xxx 3 | c | yyy (2 rows)=>
SELECT * FROM t1 LEFT JOIN t2 ON t1.num = t2.num;
num | name | num | value -----+------+-----+------- 1 | a | 1 | xxx 2 | b | | 3 | c | 3 | yyy (3 rows)=>
SELECT * FROM t1 LEFT JOIN t2 USING (num);
num | name | value -----+------+------- 1 | a | xxx 2 | b | 3 | c | yyy (3 rows)=>
SELECT * FROM t1 RIGHT JOIN t2 ON t1.num = t2.num;
num | name | num | value -----+------+-----+------- 1 | a | 1 | xxx 3 | c | 3 | yyy | | 5 | zzz (3 rows)=>
SELECT * FROM t1 FULL JOIN t2 ON t1.num = t2.num;
num | name | num | value -----+------+-----+------- 1 | a | 1 | xxx 2 | b | | 3 | c | 3 | yyy | | 5 | zzz (4 rows)
用 ON
指定的连接条件还可能包含与连接没有直接关系的条件。这会对某些查询很有用,但需要仔细考虑。例如
=>
SELECT * FROM t1 LEFT JOIN t2 ON t1.num = t2.num AND t2.value = 'xxx';
num | name | num | value -----+------+-----+------- 1 | a | 1 | xxx 2 | b | | 3 | c | | (3 rows)
请注意,将限制放在 WHERE
子句中会生成不同的结果
=>
SELECT * FROM t1 LEFT JOIN t2 ON t1.num = t2.num WHERE t2.value = 'xxx';
num | name | num | value -----+------+-----+------- 1 | a | 1 | xxx (1 row)
这是因为,ON
子句中设置的限制在连接前得到处理,而 WHERE
子句中设置的限制则在连接后得到处理。这在内连接中没有关系,但在外连接中却至关重要。
可以在表格及复杂表格引用的临时名称中指定供查询其余部分中派生表格引用的内容。这称为表格别名。
要创建表格别名,请编写
FROMtable_reference
ASalias
或者
FROMtable_reference
alias
关键词为可选择的噪音。AS
alias
可以为任意标识符。
表格别名的典型应用是,为长表格名称分配短标识符,以保持连接语句的可读性。例如
SELECT * FROM some_very_long_table_name s JOIN another_fairly_long_name a ON s.id = a.num;
此别名已成为表格引用的新名称,只要当前查询相关即可,但不得在查询中的其他地方通过原始名称来引用该表格。因此,以下内容无效
SELECT * FROM my_table AS m WHERE my_table.a > 5; -- wrong
表格别名主要用于书写便利,但在将表格连接到自身时有必要使用,例如:
SELECT * FROM people AS mother JOIN people AS child ON mother.id = child.mother_id;
使用括号来消歧义。在以下示例中,第一条语句将别名
分配给 b
的第二个实例,但第二条语句将别名分配给连接的结果my_table
SELECT * FROM my_table AS a CROSS JOIN my_table AS b ... SELECT * FROM (my_table AS a CROSS JOIN my_table) AS b ...
另一种表格别名形式为表格本身以及表格列都赋予了临时名称
FROMtable_reference
[AS]alias
(column1
[,column2
[, ...]] )
如果指定少于实际表格列数的列别名,则不会重命名剩余的列。此语法对自连接或子查询特别有用。
当将别名应用到
子句输出时,别名将隐藏 JOIN
中的原始名称。例如JOIN
SELECT a.* FROM my_table AS a JOIN your_table AS b ON ...
是有效的 SQL,但
SELECT a.* FROM (my_table AS a JOIN your_table AS b ON ...) AS c
无效;表格别名
在别名 a
外部不可见。c
指定派生表格的子查询必须放在括号内。可以为其分配表格别名名称,还可以分配列别名名称(如 第 7.2.1.2 节 中所示)。例如
FROM (SELECT * FROM table1) AS alias_name
此示例等同于
。当子查询涉及分组或聚合时,会出现更有趣的案例,这些案例不能缩减为简单连接。FROM table1 AS alias_name
子查询也可以是
列表
FROM (VALUES ('anne', 'smith'), ('bob', 'jones'), ('joe', 'blow')) AS names(first, last)
同样,表别名是可选的。向 VALUES
列表的列分配别名名称是可选的,但是一个良好的实践。有关详细信息,请参阅 7.7 节。
根据 SQL 标准,必须为子查询提供表别名名称。PostgreSQL 允许省略 AS
和别名,但在可能移植到其他系统的 SQL 代码中编写别名是一个良好的实践。
表函数是生成一组行(由基本数据类型(标量类型)或复合数据类型(表行)组成)的函数。它们在查询的 FROM
子句中用作表、视图或子查询。表函数返回的列可以与表的列、视图或子查询的列一样,包括在 SELECT
、JOIN
或 WHERE
子句中。
还可以使用 ROWS FROM
语法组合表函数,并以并行列返回结果;在这种情况下,结果行的数量是函数结果中最大的那个,结果较小的函数用空值填充以进行匹配。
function_call
[WITH ORDINALITY] [[AS]table_alias
[(column_alias
[, ... ])]] ROWS FROM(function_call
[, ... ] ) [WITH ORDINALITY] [[AS]table_alias
[(column_alias
[, ... ])]]
如果指定了 WITH ORDINALITY
子句,将向函数结果列添加一个附加的类型为 bigint
的列。此列对函数结果集的行进行编号,从 1 开始。(这是 UNNEST ... WITH ORDINALITY
的 SQL 标准语法的概括。)默认情况下,序数列称为 ordinality
,但可以使用 AS
子句为它分配不同的列名。
特殊的表函数 UNNEST
可以通过任意数量的数组参数来调用,并且返回相应数量的列,就像在每个参数上单独调用 UNNEST
(第 9.19 节)并使用 ROWS FROM
构造组合一样。
UNNEST(array_expression
[, ... ] ) [WITH ORDINALITY] [[AS]table_alias
[(column_alias
[, ... ])]]
如果没有指定 table_alias
,则函数名称用作表名;在 ROWS FROM()
构造的情况下,使用第一个函数的名称。
如果没有提供列别名,那么对于返回基本数据类型的函数,列名也与函数名相同。对于返回复合类型的函数,结果列获取该类型的各个属性的名称。
一些示例
CREATE TABLE foo (fooid int, foosubid int, fooname text); CREATE FUNCTION getfoo(int) RETURNS SETOF foo AS $$ SELECT * FROM foo WHERE fooid = $1; $$ LANGUAGE SQL; SELECT * FROM getfoo(1) AS t1; SELECT * FROM foo WHERE foosubid IN ( SELECT foosubid FROM getfoo(foo.fooid) z WHERE z.fooid = foo.fooid ); CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo;
在有些情况下,定义会根据调用的方式返回不同列集的表函数很有用。为了支持这种情况,可以将表函数声明为返回伪类型 record
,且没有 OUT
参数。当在查询中使用此类函数时,必须在查询中指定预期行结构,以便系统能够了解如何解析和计划查询。语法如下
function_call
[AS]alias
(column_definition
[, ... ])function_call
AS [alias
] (column_definition
[, ... ]) ROWS FROM( ...function_call
AS (column_definition
[, ... ]) [, ... ] )
在不使用 ROWS FROM()
语法时,column_definition
列表替换可能附加到 FROM
项的列别名列表;列定义中的名称用作列别名。在使用 ROWS FROM()
语法时,column_definition
列表可以分别附加到每个成员函数;或者如果只有一个成员函数且没有 WITH ORDINALITY
子句,可以编写一个 column_definition
列表,代替 ROWS FROM()
后面的列别名列表。
请考虑以下示例
SELECT * FROM dblink('dbname=mydb', 'SELECT proname, prosrc FROM pg_proc') AS t1(proname name, prosrc text) WHERE proname LIKE 'bytea%';
dblink 函数(a 部分的 dblink dblink 模块)执行远程查询。声明它返回 record
,因为它可能用于任何类型的查询。必须在调用查询中指定实际的列集,以便解析器知道,例如 *
应该扩展为哪一项。
此示例使用 ROWS FROM
SELECT * FROM ROWS FROM ( json_to_recordset('[{"a":40,"b":"foo"},{"a":"100","b":"bar"}]') AS (a INTEGER, b TEXT), generate_series(1, 3) ) AS x (p, q, s) ORDER BY p; p | q | s -----+-----+--- 40 | foo | 1 100 | bar | 2 | | 3
它将两个函数联接到一个 FROM
目标。指示 json_to_recordset()
返回两列,第一列为 integer
,第二列为 text
。直接使用 generate_series()
的结果。ORDER BY
子句按列值作为整数对其排序。
LATERAL
子查询 #出现在 FROM
中的子查询可在前面添加关键单词 LATERAL
。这允许它们引用前面提供 FROM
项的列。(不使用 LATERAL
时,每个子查询都会独立评估,因此无法交叉引用任何其他 FROM
项。)
出现在 FROM
中的表函数也可以使用关键字 LATERAL
,但是对于函数来说,该关键字是可选的。无论如何,该函数的参数可以包含对 FROM
项目提供的列的引用。
一个 LATERAL
项目可以出现在 FROM
列表的第一层,或者是在 JOIN
树中。在后一种情况下,它还可以引用位于 JOIN
左侧的项目,而它位于该右侧。
当一个 FROM
项目包含 LATERAL
交叉引用时,计算的步骤如下:对于交叉引用列或提供列的多个 FROM
项目的行集中的每一行,LATERAL
项目使用该行或行集的列值进行计算。所得行与计算出这些行的行照常连接。对于列源表中的每一行或行集,此步骤都会重复进行。
LATERAL
的一个简单示例是:
SELECT * FROM foo, LATERAL (SELECT * FROM bar WHERE bar.id = foo.bar_id) ss;
这不是特别有用的,因为它与惯例完全相同
SELECT * FROM foo, bar WHERE bar.id = foo.bar_id;
LATERAL
主要在交叉引用列对于计算要连接的行数是必要的时候才使用。典型的应用是为返回集合的函数提供参数值。例如,假设 vertices(polygon)
返回一个多边形的顶点集,我们可以使用下述方法识别表格中存储的、距离非常近的多边形的顶点:
SELECT p1.id, p2.id, v1, v2 FROM polygons p1, polygons p2, LATERAL vertices(p1.poly) v1, LATERAL vertices(p2.poly) v2 WHERE (v1 <-> v2) < 10 AND p1.id != p2.id;
这个查询还可以编写为
SELECT p1.id, p2.id, v1, v2 FROM polygons p1 CROSS JOIN LATERAL vertices(p1.poly) v1, polygons p2 CROSS JOIN LATERAL vertices(p2.poly) v2 WHERE (v1 <-> v2) < 10 AND p1.id != p2.id;
或者其他几种等效的表述。(如前所述,在这个例子中 LATERAL
关键字是多余的,但我们为了清晰起见使用它。)
常常特别便于 LEFT JOIN
的目标是一个 LATERAL
子查询,这样源行将出现在结果中,即使 LATERAL
子查询不为它们生成任何行。例如,如果 get_product_names()
返回制造商制造的产品的名称,但我们表中的某些制造商当前不生产任何产品,我们就可以像这样找出这些制造商:
SELECT m.name FROM manufacturers m LEFT JOIN LATERAL get_product_names(m.id) pname ON true WHERE pname IS NULL;
WHERE
子句 #WHERE
子句的语法为
WHERE search_condition
其中 search_condition
是任何值表达式(见 4.2 节),它返回 boolean
类型的值。
在 FROM
子句的处理完成后,导出的虚拟表的每行将针对搜索条件进行检查。如果条件的结果为真,则将该行保存在输出表中,否则(即,如果结果为假或空),则将其丢弃。该搜索条件通常引用在 FROM
子句中生成的表的至少一列;此非必需,否则 WHERE
子句将相当无用。
内部联接的联接条件可以写入 WHERE
子句或 JOIN
子句。例如,这些表表达式是等效的
FROM a, b WHERE a.id = b.id AND b.val > 5
和
FROM a INNER JOIN b ON (a.id = b.id) WHERE b.val > 5
或者可能还有
FROM a NATURAL JOIN b WHERE b.val > 5
您使用哪一种主要取决于样式。FROM
子句中的 JOIN
语法可能无法移植到其他 SQL 数据库管理系统中,尽管它在 SQL 标准中。对于外部联接,没有选择:它们必须在 FROM
子句中执行。ON
或 USING
外部联接的子句不同于 WHERE
条件,因为它导致添加行(对于不匹配的输入行)以及删除最终结果中的行。
以下列出部分 WHERE
子句的示例
SELECT ... FROM fdt WHERE c1 > 5 SELECT ... FROM fdt WHERE c1 IN (1, 2, 3) SELECT ... FROM fdt WHERE c1 IN (SELECT c1 FROM t2) SELECT ... FROM fdt WHERE c1 IN (SELECT c3 FROM t2 WHERE c2 = fdt.c1 + 10) SELECT ... FROM fdt WHERE c1 BETWEEN (SELECT c3 FROM t2 WHERE c2 = fdt.c1 + 10) AND 100 SELECT ... FROM fdt WHERE EXISTS (SELECT c1 FROM t2 WHERE c2 > fdt.c1)
fdt
是 FROM
子句中导出的表。不符合 WHERE
子句的搜索条件的行将从 fdt
中移除。注意将标量子查询用作值表达式。恰如其他任何查询,子查询可以使用复杂表表达式。还要注意 fdt
在子查询中引用的方式。仅当 c1
也是子查询衍生输入表中某列的名称时,将 c1
限定为 fdt.c1
才必要。但即使不需要,限定列名称也会增强清晰度。此示例说明了外部查询的列命名范围是如何扩展到其内部查询中的。
GROUP BY
和 HAVING
子句 #通过 WHERE
筛选后,衍生输入表可能会使用 GROUP BY
子句进行分组,并使用 HAVING
子句消除组行。
SELECTselect_list
FROM ... [WHERE ...] GROUP BYgrouping_column_reference
[,grouping_column_reference
]...
使用 GROUP BY
子句可以将表中所有在所列所有列中具有相同值的行分组在一起。列出的列顺序无关紧要。其效果是将每组具有公共值的众多行组合到一组行中,代表该组中的所有行。这样做是为了消除输出中的冗余和/或计算适用于这些组的聚合。例如
=>
SELECT * FROM test1;
x | y ---+--- a | 3 c | 2 b | 5 a | 1 (4 rows)=>
SELECT x FROM test1 GROUP BY x;
x --- a b c (3 rows)
在第二个查询中,我们无法编写 SELECT * FROM test1 GROUP BY x
,因为没有可以与每个组关联的 y
列的单个值。可以引用组列的选择列表,因为它们在每个组中具有单个值。
一般来说,如果一张表被分组,除 GROUP BY
中列出的列之外的列不能被引用,除非在聚合表达式中。使用聚合表达式的示例如下
=>
SELECT x, sum(y) FROM test1 GROUP BY x;
x | sum ---+----- a | 4 b | 5 c | 2 (3 rows)
这里 sum
是一个聚合函数,用于计算整个组中的单个值。有关可用聚合函数的更多信息,请参见 第 9.21 节。
不使用聚合表达式的分组实际上计算了一列中一组不同的值。也可以使用 DISTINCT
子句(请参见 第 7.3.3 节)实现此操作。
以下为另一个示例:它计算每个产品的总销售额(而不是所有产品的总销售额)
SELECT product_id, p.name, (sum(s.units) * p.price) AS sales FROM products p LEFT JOIN sales s USING (product_id) GROUP BY product_id, p.name, p.price;
在此示例中,列 product_id
、p.name
和 p.price
必须在 GROUP BY
子句中,因为它们在查询选择列表中被引用(但请参见下面)。列 s.units
不必在 GROUP BY
列表中,因为它只用于聚合表达式(sum(...)
)中,表示某个产品的销售额。对于每个产品,查询会返回该产品所有销售额的汇总行。
如果 products 表已设置完毕,例如,product_id
是主键,那么在上例中它足以按 product_id
分组,因为 name 和 price 在形式上依赖于 product ID,因此对于要为每个 product ID 组返回的 name 和 price 值,不会产生歧义。
在严格的 SQL 中,GROUP BY
可以按源表的列进行分组,但 PostgreSQL 将其扩展,允许 GROUP BY
也按选择列表中的列进行分组。同时,也可以按值表达式而不是简单的列名进行分组。
如果某个表已使用 GROUP BY
进行了分组,但只对某些组感兴趣,可以使用 HAVING
子句(很多像 WHERE
子句)从结果中消除这些组。其语法为
SELECTselect_list
FROM ... [WHERE ...] GROUP BY ... HAVINGboolean_expression
HAVING
子句中的表达式既可以指组表达式,也可以指非组表达式(必然涉及汇总函数)。
例如
=>
SELECT x, sum(y) FROM test1 GROUP BY x HAVING sum(y) > 3;
x | sum ---+----- a | 4 b | 5 (2 rows)=>
SELECT x, sum(y) FROM test1 GROUP BY x HAVING x < 'c';
x | sum ---+----- a | 4 b | 5 (2 rows)
当然,还有一个更贴近实际的例子
SELECT product_id, p.name, (sum(s.units) * (p.price - p.cost)) AS profit FROM products p LEFT JOIN sales s USING (product_id) WHERE s.date > CURRENT_DATE - INTERVAL '4 weeks' GROUP BY product_id, p.name, p.price, p.cost HAVING sum(p.price * s.units) > 5000;
在上述示例中,WHERE
子句按未分组的列选择行(该表达式仅对过去四星期的销售额为真),而 HAVING
子句将输出限制为总毛销售额超过 5000 的组。注意,汇总表达式不一定在查询的所有部分中都是相同的。
如果某个查询包含汇总函数调用但没有 GROUP BY
子句,仍然会出现分组:结果是一个单独的组行(或者可能完全没有行,如果该单独行随后被 HAVING
所消除)。如果查询包含 HAVING
子句,即使没有任何汇总函数调用或 GROUP BY
子句,情况也是一样。
GROUPING SETS
、CUBE
和 ROLLUP
#可以使用分组集的概念执行比上面所述更复杂的分组操作。由 FROM
和 WHERE
子句选择的数据按指定的每个分组集单独分组,为每个组计算汇总值(就像是对简单的 GROUP BY
子句一样),然后返回结果。例如
=>
SELECT * FROM items_sold;
brand | size | sales -------+------+------- Foo | L | 10 Foo | M | 20 Bar | M | 15 Bar | L | 5 (4 rows)=>
SELECT brand, size, sum(sales) FROM items_sold GROUP BY GROUPING SETS ((brand), (size), ());
brand | size | sum -------+------+----- Foo | | 30 Bar | | 20 | L | 15 | M | 35 | | 50 (5 rows)
每个 GROUPING SETS
的子列表可以指定零个或更多列或表达式,并且解释它们的含义与直接放在 GROUP BY
子句中的相同。空的组集表示所有行都已经汇总到一个组中(即使没有输入行,也输出该组),正如上面在没有 GROUP BY
子句的情况下使用汇总函数的情况中所述。
对分组列或表达式的引用会在这些列未显示的分组集中替换为结果行中的 Null 值。要区分特定输出行从哪个分组中产生的,请参阅表 9.64。
提供了一个简写符号,用于指定两种常见的分组集类型。形式为
ROLLUP (e1
,e2
,e3
, ... )
表示给定的表达式列表和所有列表前缀,包括空列表;因此相当于
GROUPING SETS ( (e1
,e2
,e3
, ... ), ... (e1
,e2
), (e1
), ( ) )
此函数通常用于针对分层数据进行分析;例如,按部门、部门和全公司计算总薪资。
形式的条款
CUBE (e1
,e2
, ... )
表示给定的列表和所有可能的子集(即幂集)。因此
CUBE ( a, b, c )
相当于
GROUPING SETS ( ( a, b, c ), ( a, b ), ( a, c ), ( a ), ( b, c ), ( b ), ( c ), ( ) )
CUBE
或 ROLLUP
条款中的各个元素可以是单独的表达式,或者括号中元素的子列表。在后一种情况下,在生成各个分组集合时,子列表将被视为单一单位。例如
CUBE ( (a, b), (c, d) )
相当于
GROUPING SETS ( ( a, b, c, d ), ( a, b ), ( c, d ), ( ) )
和
ROLLUP ( a, (b, c), d )
相当于
GROUPING SETS ( ( a, b, c, d ), ( a, b, c ), ( a ), ( ) )
CUBE
和 ROLLUP
构造可以单独在 GROUP BY
条款中使用,或者嵌套在 GROUPING SETS
条款中。如果一个 GROUPING SETS
条款嵌套在另一个条款中,效果与将内部条款的所有元素直接写入外部条款相同。
如果在单个 GROUP BY
条款中指定了多个分组项,则分组集合的最终列表是各个项的笛卡尔积。例如
GROUP BY a, CUBE (b, c), GROUPING SETS ((d), (e))
相当于
GROUP BY GROUPING SETS ( (a, b, c, d), (a, b, c, e), (a, b, d), (a, b, e), (a, c, d), (a, c, e), (a, d), (a, e) )
在共同指定多个分组项时,分组集合的最终集合可能包含重复项。例如
GROUP BY ROLLUP (a, b), ROLLUP (a, c)
相当于
GROUP BY GROUPING SETS ( (a, b, c), (a, b), (a, b), (a, c), (a), (a), (a, c), (a), () )
如果这些重复项是不可取的,可以使用 DISTINCT
条款直接在 GROUP BY
上将其移除。因此
GROUP BY DISTINCT ROLLUP (a, b), ROLLUP (a, c)
相当于
GROUP BY GROUPING SETS ( (a, b, c), (a, b), (a, c), (a), () )
这与使用 SELECT DISTINCT
不同,因为输出行可能仍包含重复项。如果任何未分组列包含 NULL,则它将与在对同一列进行分组时使用的 NULL 无法区分。
构造 (a, b)
通常在表达式中识别为 行构造函数。在 GROUP BY
条款中,这并不适用于表达式的顶级,并且 (a, b)
将被解析为如上所述的表达式列表。如果您出于某种原因需要在分组表达式中使用行构造函数,请使用 ROW(a, b)
。
如果查询包含任何窗口函数(见章节 3.5、章节 9.22和章节 4.2.8),在执行任何分组、聚集及HAVING
过滤后,再评估这些函数。也就是说,如果查询使用任何聚集、GROUP BY
或HAVING
,则窗口函数看到的行是组行,而不是FROM
/WHERE
的原始表行。
如果使用多个窗口函数,则在窗口定义中具有语法等效PARTITION BY
和ORDER BY
子句的所有窗口函数保证一次性评估数据。因此,它们将看到相同的排序顺序,即使ORDER BY
未唯一确定顺序。但是,对于具有不同的PARTITION BY
或ORDER BY
规范的函数的评估,不作任何保证。(在这种情况下,窗口函数评估通过之间通常需要一个排序步骤,并且该排序不能保证按其ORDER BY
视为相等的行的顺序排列。)
目前,窗口函数总是需要经过预先排序的数据,因此查询输出将按照窗口函数的PARTITION BY
/ORDER BY
子句之一进行排序。但是,不建议依赖于此。如果要确保结果以特定方式排序,请使用显式的顶级ORDER BY
子句。