表表达式用于计算出一个表。表表达式包含一个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外面是看不到的。
指定派生表的子查询必须用圆括号括起来。它们可以指定表别名,也可以可选地指定列别名(参见Section 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列表中的列分配别名也是可选的,但这是一个好习惯。更多信息可参见Section 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(Section 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类型值的值表达式(参阅Section 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是一个聚合函数,它在整个组上计算出一个单一值。有关可用的聚合函数的更多信息可以在Section 9.21。
不带聚合表达式的分组实际上是在计算某一列的不同值集合。这也可以用DISTINCT子句实现(参阅Section 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子句的聚合函数的情况一样。
对于分组列或表达式没有出现在其中的分组集的结果行,对分组列或表达式的引用会 被空值所替代。要区分一个特定的输出行来自于哪个分组,请见 Table 9.63。
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)。
如果查询包含任何窗口函数(见Section 3.5、Section 9.22和Section 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子句。
如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。