一个 表表达式 计算一个表。表表达式包含一个 FROM
子句,后面可以选择跟着 WHERE
、GROUP BY
和 HAVING
子句。简单的表表达式只是引用磁盘上的一个表,也就是所谓的基表,但更复杂的表达式可以用来以各种方式修改或组合基表。
表表达式中可选的 WHERE
、GROUP BY
和 HAVING
子句指定了在 FROM
子句中导出的表上执行的一系列连续转换。所有这些转换都会生成一个虚拟表,该表提供传递到 select 列表以计算查询输出行的行。
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
隐式指定。连接条件确定来自两个源表的哪些行被认为是 “匹配”,如下所述。
限定连接的可能类型为
内部连接
对于 T1 的每一行 R1,连接表对 T2 中满足与 R1 的连接条件的每一行都有一个行。
LEFT OUTER JOIN
首先,执行内部连接。然后,对于 T1 中不满足与 T2 中任何行连接条件的每一行,都会添加一个连接行,其中 T2 的列中包含空值。因此,连接表始终至少包含 T1 中每一行的一个行。
RIGHT OUTER JOIN
首先,执行内部连接。然后,对于 T2 中不满足与 T1 中任何行连接条件的每一行,都会添加一个连接行,其中 T1 的列中包含空值。这与左连接相反:结果表始终包含 T2 中每一行的一个行。
完全外部连接
首先,执行内部连接。然后,对于 T1 中不满足与 T2 中任何行连接条件的每一行,都会添加一个连接行,其中 T2 的列中包含空值。此外,对于 T2 中不满足与 T1 中任何行连接条件的每一行,都会添加一个连接行,其中 T1 的列中包含空值。
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
语法组合,结果以并行列返回;在这种情况下,结果行的数量是最大函数结果的数量,较小的结果用空值填充以匹配。
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
子句,则可以在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
也是子查询的派生输入表中某一列的名称时,才需要将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
分组就足够了,因为名称和价格将功能依赖于产品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
子句的情况。
对于那些列未出现在其中的分组集的结果行,分组列或表达式的引用将被替换为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
子句。
如果您在文档中发现任何错误,与您对特定功能的体验不符,或需要进一步澄清,请使用此表单 报告文档问题。