一个 表表达式 计算出一个表。表表达式包含一个 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,连接表都有一个行,该行对应于满足 R1 的连接条件的 T2 中的每一行。
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
子句中使用的表达式是同一种类型。来自 T1
和 T2
的行对匹配,如果 ON
表达式求值为 true。
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 开始。(这是 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;
在某些情况下,定义可以根据调用方式返回不同列集的表函数是有用的。为了支持这一点,表函数可以被声明为返回伪类型 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
是任何值表达式(参见 第 4.2 节),该表达式返回 boolean
类型的值。
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
子句与 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
仅在 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
列的值可以与每个组关联。分组的列可以在 select 列表中引用,因为它们在每个组中都有一个值。
一般来说,如果一个表被分组,不能出现在 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
子句按非分组列选择行(表达式仅对过去四周的销售额为 true),而 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.66。
提供了简写表示法来指定两种常见的组集类型。形式为
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
子句嵌套在另一个 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
子句。
如果您在文档中看到任何不正确的内容,与您对特定功能的经验不符,或需要进一步澄清,请使用 此表单 报告文档问题。