表表达式计算一个表。表表达式包含一个 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,连接表都有 T2 中满足与 R1 的连接条件的每一行的行。
LEFT OUTER JOIN
首先,执行内部连接。然后,对于 T1 中不满足与 T2 中任何行的连接条件的每一行,都会添加一个连接行,其中 T2 的列中包含 null 值。因此,连接表始终具有 T1 中每一行的至少一行。
RIGHT OUTER JOIN
首先,执行内部连接。然后,对于 T2 中不满足与 T1 中任何行的连接条件的每一行,都会添加一个连接行,其中 T1 的列中包含 null 值。这是左连接的逆操作:结果表将始终具有 T2 中每一行的行。
FULL OUTER JOIN
首先,执行内部连接。然后,对于 T1 中不满足与 T2 中任何行的连接条件的每一行,都会添加一个连接行,其中 T2 的列中包含 null 值。此外,对于 T2 中不满足与 T1 中任何行的连接条件的每一行,还会添加一个连接行,其中 T1 的列中包含 null 值。
ON
子句是最通用的连接条件:它采用与 WHERE
子句中使用的相同类型的布尔值表达式。如果 ON
表达式的计算结果为 true,则来自 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
。当子查询涉及分组或聚合时,会出现更复杂的案例,这些案例无法简化为简单的连接。
子查询也可以是 VALUES
列表
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
语法进行组合,结果在并行列中返回;在这种情况下,结果行的数量是最大函数结果的数量,较小的结果会用 null 值填充以进行匹配。
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 函数(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
项的每一行,或者提供列的多个 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
是返回 boolean
类型值的任何值表达式(请参阅 第 4.2 节)。
在完成 FROM
子句的处理后,将对照搜索条件检查导出的虚拟表的每一行。如果条件的结果为 true,则该行保留在输出表中,否则(即,如果结果为 false 或 null)则丢弃该行。搜索条件通常至少引用在 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
子句与 not 等效于 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(...)
) 中使用,该表达式表示产品的销售额。对于每个产品,查询返回有关该产品所有销售额的摘要行。
如果产品表设置得使 product_id
为主键,那么在上面的示例中,仅按 product_id
分组就足够了,因为名称和价格将 函数式依赖于产品 ID,因此对于每个产品 ID 组,返回哪个名称和价格值没有歧义。
在严格的 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
子句的聚合函数的情况。
对于未出现在分组集中的列,对分组列或表达式的引用将替换为结果行中的空值。要区分特定输出行来自哪个分组,请参见 表 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
子句。
如果您在文档中发现任何不正确、与您使用特定功能的体验不符或需要进一步澄清的内容,请使用此表格报告文档问题。