pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
表表达式用于计算出一个表。表表达式包含一个 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,而是在表名后面写上*,显式指定要包含后代表。如今继续使用这种语法并没有实际理由,因为包含后代表现在本来就是默认行为。不过,为了兼容旧版本,仍然支持这种语法。
一个连接表是根据特定的连接类型的规则从两个其他表(真实表或派生表)中派生的表。目前支持内连接、外连接和交叉连接。一个连接表的一般语法是:
T1join_typeT2[join_condition]
所有类型的连接都可以串接在一起或者嵌套:T1 和 T2 都可以是连接表。在 JOIN 子句周围可以使用圆括号来控制连接顺序。如果不使用圆括号,JOIN 子句会从左至右嵌套。
连接类型
T1CROSS JOINT2
对来自于 T1 和 T2 的行的每一种可能的组合(即笛卡尔积),连接表将包含这样一行:它由 T1 中的所有列以及紧随其后的 T2 中的所有列构成。如果两个表分别有 N 和 M 行,连接表将有 N * M 行。
FROM 等效于 T1 CROSS JOIN T2FROM (见下文)。它也等效于 T1 INNER JOIN T2 ON TRUEFROM 。T1,T2
当出现两个以上的表时,后一种等价关系并不严格成立,因为 JOIN 的绑定强于逗号。例如 FROM 和 T1 CROSS JOIN T2 INNER JOIN T3 ON conditionFROM 并不完全相同,因为第一种情况中的 T1,T2 INNER JOIN T3 ON conditioncondition 可以引用 T1,而第二种情况中则不行。
T1{ [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOINT2ONboolean_expressionT1{ [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOINT2USING (join column list)T1NATURAL { [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 的列用空值补齐。因此,生成的连接表里为来自 T1 的每一行都至少包含一行。
RIGHT OUTER JOIN #首先,执行一次内连接。然后,为 T2 中每一个无法在连接条件上匹配 T1 里任何一行的行返回一个连接行,该连接行中 T1 的列用空值补齐。这与左连接相反:生成的连接表里为来自 T2 的每一行都至少包含一行。
FULL OUTER JOIN #首先,执行一次内连接。然后,为 T1 中每一个无法在连接条件上匹配 T2 里任何一行的行返回一个连接行,该连接行中 T2 的列用空值补齐。同样,为 T2 中每一个无法在连接条件上匹配 T1 里任何一行的行返回一个连接行,该连接行中 T1 的列用空值补齐。
ON 子句是最通用的连接条件形式:它接收一个和 WHERE 子句里用的一样的布尔值表达式。如果两个分别来自 T1 和 T2 的行在 ON 表达式上运算的结果为真,那么它们就算是匹配的行。
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_referenceASalias
或者
FROMtable_referencealias
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; -- 错误
表别名主要用于简化符号,但是当把一个表连接到它自身时必须使用别名,例如:
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 语法把多个表函数组合起来,并列返回各函数的结果列;这种情况下,结果行数等于返回行数最多的那个函数的结果行数,较小的结果会用空值填充到相同长度。
function_call[WITH ORDINALITY] [[AS]table_alias[(column_alias[, ... ])]] ROWS FROM(function_call[, ... ] ) [WITH ORDINALITY] [[AS]table_alias[(column_alias[, ... ])]]
如果指定了 WITH ORDINALITY 子句,一个额外的 bigint 类型的列将会被增加到函数的结果列中。这个列对函数结果集的行进行编号,编号从 1 开始(这是对 SQL 标准语法 UNNEST ... WITH ORDINALITY 的一般化)。默认情况下,序数列被称为 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;
有时候,定义一个能够根据调用方式返回不同列集合的表函数很有用。为支持这一点,可以将不带 OUT 参数的表函数声明为返回伪类型 record。当此类函数在查询中使用时,必须在查询本身中指定预期的行结构,这样系统才能知道如何解析和规划该查询。这种语法如下:
function_call[AS]alias(column_definition[, ... ])function_callAS [alias] (column_definition[, ... ]) ROWS FROM( ...function_callAS (column_definition[, ... ]) [, ... ] )
在不使用 ROWS FROM() 语法时,column_definition 列表会取代原本可附加到 FROM 项上的列别名列表,列定义中的名称就起到列别名的作用。在使用 ROWS FROM() 语法时,可以为每一个成员函数单独附着一个 column_definition 列表;或者在只有一个成员函数并且没有 WITH ORDINALITY 子句的情况下,可以在 ROWS FROM() 后面写一个 column_definition 列表来取代一个列别名列表。
考虑下面的示例:
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 子句的处理之后,生成的虚拟表中的每一行都会根据搜索条件进行检查。如果条件结果为真,该行就会保留在输出表中;否则(也就是说,如果结果为假或空)就会被丢弃。搜索条件通常至少会引用 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 同时也是子查询输入表的某个列名时,才必须把它写成 fdt.c1。不过,即使不是必须的,限定列名通常也能使语句更清晰。这个示例展示了外层查询的列名作用域如何扩展到其内层查询中。
GROUP BY 和 HAVING 子句 #通过 WHERE 过滤之后,派生出来的输入表还可以使用 GROUP BY 子句进行分组,并通过 HAVING 子句排除某些分组行。
SELECTselect_listFROM ... [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 分组就足够了,因为名称和价格都函数依赖于产品 ID,因此对于每个产品 ID 分组该返回哪个名称和价格值不会有歧义。
在严格的 SQL 里,GROUP BY 只能对源表的列进行分组,但 PostgreSQL 把这个扩展为也允许 GROUP BY 去根据选择列表中的列分组。也允许对值表达式进行分组,而不仅是简单的列名。
如果一个表已经用 GROUP BY 子句分了组,而你只对其中某些组感兴趣,那么就可以使用 HAVING 子句。它很像 WHERE 子句,用于从结果中删除某些组。其语法是:
SELECTselect_listFROM ... [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.66。
PostgreSQL 中提供了一种简化方法来指定两种常用类型的分组集。下面形式的子句
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),
()
)
如果这些重复项是不希望出现的,可以在 GROUP BY 上直接使用 DISTINCT 子句来删除它们。因此:
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 子句。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。英文原文本身的问题请通过上游文档表单反馈给 PostgreSQL 文档维护者。