7.2. 表表达式

表表达式计算表。该表表达式包含一个FROM子句,并可选地跟着WHEREGROUP BYHAVING子句。琐碎的表表达式仅引用磁盘上的表,即所谓的基表,但更复杂的表达式可用于以各种方式修改或组合基表。

表表达式中的可选WHEREGROUP BYHAVING子句指定对在FROM子句中派生的表执行的连续转换的管道。所有这些转换都会生成一个虚拟表,该表提供传递到选择列表的行,以计算查询的输出行。

7 .2.1. FROM 子句

FROM Clause从一个逗号分隔的表参考列表中给出的一个或多个其他表中派生一个表。

FROM table_reference [, table_reference [, ...]]

表引用可以是表名称(可能是架构限定的),也可以是派生表,例如子查询,JOIN构造或这些的复杂组合。如果FROM子句中列出了多个表引用,则表将交叉连接(即,形成了其行的笛卡尔积;请参见下文)。 FROM列表的结果是一个中间虚拟表,然后可以通过WHEREGROUP BYHAVING子句进行转换,并且最终是整个表表达式的结果。

当表引用命名作为表继承层次结构的父级的表时,该表引用不仅会生成该表的行,还会生成其所有后代表的行,除非关键字ONLY位于表名之前。但是,该引用仅产生出现在命名表中的列-子表中添加的任何列都将被忽略。

您可以在表名之后写*来明确指定包含后代表,而不必在表名之前写ONLY。没有 true 的理由再使用此语法,因为搜索后代表现在始终是默认行为。但是,支持它与较早版本兼容。

7 .2.1.1. 联接表

联接表是根据特定联接类型的规则从其他两个(真实或派生)表派生的表。提供内部,外部和交叉连接。联接表的一般语法是

T1 join_type T2 [ join_condition ]

所有类型的联接都可以链接在一起或嵌套:* T1 T2 *中的一个或两个都可以联接表。可以在JOIN子句前后使用括号来控制连接 Sequences。在没有括号的情况下,JOIN子句从左到右嵌套。

Join Types

  • 交叉加入
T1 CROSS JOIN T2

对于* T1 T2 (即笛卡尔积)中的行的每种可能组合,联接的表将包含一行,该行由 T1 中的所有列组成,然后是 T2 *中的所有列。如果表分别具有 N 行和 M 行,则联接表将具有 N * M 行。

FROM T1 CROSS JOIN T2等效于FROM T1 INNER JOIN T2 ON TRUE(请参见下文)。它也等效于FROM T1, T2

Note

当出现两个以上的表时,后一种等效项并不完全成立,因为JOIN的绑定比逗号更紧密。例如FROM T1 CROSS JOIN T2 INNER JOIN T3 ON conditionFROM T1, T2 INNER JOIN T3 ON condition不同,因为* condition 在第一种情况下可以引用 T1 *,但在第二种情况下不能。

  • 合格的加入
T1 { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2 ON boolean_expression
T1 { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2 USING ( join column list )
T1 NATURAL { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2

INNEROUTER一词在所有形式中都是可选的。 INNER是默认值; LEFTRIGHTFULL表示外部联接。

  • join 条件*在ONUSING子句中指定,或由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 的列中添加具有空值的联接行。此外,对于不满足与 T1 中任何行的联接条件的 T2 的每一行,将添加 T1 列中具有空值的联接行。

ON子句是最通用的连接条件:它采用与WHERE子句中使用的布尔值类型相同的布尔值表达式。如果ON表达式的计算结果为 true,则* T1 T2 *中的一对行匹配。

USING子句是一种简写形式,可让您利用特定的情况,即连接的双方对连接列使用相同的名称。它采用逗号分隔的共享列名称列表,并形成一个连接条件,其中包括每个条件的相等性比较。例如,将* T1 T2 *与USING (a, b)连接会产生连接条件ON T1.a = T2.a AND T1.b = T2.b

此外,JOIN USING的输出抑制了多余的列:无需打印两个匹配的列,因为它们必须具有相等的值。虽然JOIN ON产生* T1 的所有列,然后产生 * T2 的所有列,但JOIN USING为每个列出的列对(按列出的 Sequences)产生一个输出列,接着是 T1 的所有剩余列,然后是任何 T2 *中剩余的列。

最后,NATURALUSING的简写形式:它形成USING列表,该列表由两个 Importing 表中出现的所有列名组成。与USING一样,这些列在输出表中仅出现一次。如果没有通用的列名,则NATURAL JOIN的行为类似于JOIN ... ON TRUE,从而产生跨产品联接。

Note

由于仅合并了列出的列,因此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子句中的限制是在联接之后处理的。这与内部联接无关紧要,但对于外部联接则非常重要。

7 .2.1.2. 表和列别名

可以为表和复杂表引用赋予一个临时名称,以用于在其余查询中对派生表的引用。这称为表别名

要创建表别名,请编写

FROM table_reference AS alias

or

FROM table_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;

另外,如果表引用是子查询,则需要别名(请参见Section 7.2.1.3)。

括号用于解决歧义。在以下示例中,第一条语句将别名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 ...

表别名的另一种形式为表的列以及表本身赋予临时名称:

FROM table_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.3. Subqueries

指定派生表的子查询必须用括号括起来,并且必须为表分配别名(如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

7 .2.1.4. 表功能

表函数是产生一组行的函数,这些行由基本数据类型(标量类型)或复合数据类型(表行)组成。在查询的FROM子句中,它们像表,视图或子查询一样使用。表函数返回的列可以以与表,视图或子查询的列相同的方式包含在SELECTJOINWHERE子句中。

表函数也可以使用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(Section 9.18)并使用ROWS FROM构造将其组合在一起一样。

UNNEST( array_expression [, ... ] ) [WITH ORDINALITY] [[AS] table_alias [(column_alias [, ... ])]]

如果未指定* table_alias *,则将函数名用作表名;否则,将函数名用作表名。如果是ROWS FROM()构造,则使用第一个函数的名称。

如果未提供列别名,则对于返回基本数据类型的函数,列名也与函数名相同。对于返回复合类型的函数,结果列将获取该类型的各个属性的名称。

Some examples:

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。在查询中使用这种功能时,必须在查询本身中指定预期的行结构,以便系统知道如何解析和计划查询。该语法如下:

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,因为它可以用于任何类型的查询。必须在调用查询中指定实际的列集,以便解析器知道*应该扩展为什么。

7 .2.1.5. 横向子查询

出现在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 JOINLATERAL子查询通常特别方便,因此即使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;

7 .2.2. WHERE 条款

WHERE Clause的语法是

WHERE search_condition

其中* search_condition *是返回类型为boolean的值的任何值表达式(请参见Section 4.2)。

完成FROM子句的处理后,将对照搜索条件检查派生虚拟表的每一行。如果条件的结果为 true,则将该行保留在输出表中,否则(即,如果结果为 false 或 null)将其丢弃。搜索条件通常引用FROM子句中生成的表的至少一列;这不是必需的,否则WHERE子句将毫无用处。

Note

内部联接的联接条件可以写在WHERE子句或JOIN子句中。例如,这些表表达式是等效的:

FROM a, b WHERE a.id = b.id AND b.val > 5

and:

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 数据库 Management 系统中,即使它在 SQL 标准中。对于外部联接,别无选择:它们必须在FROM子句中完成。外部联接的ONUSING子句等效于WHERE条件,因为它会导致增加行(对于不匹配的 Importing 行)以及删除最终结果中的行。

以下是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)

fdtFROM子句中派生的表。不满足WHERE子句搜索条件的行将从fdt中消除。注意将标量子查询用作值表达式。就像任何其他查询一样,子查询可以使用复杂的表表达式。还要注意在子查询中如何引用fdt。仅当c1也是子查询的派生 Importing 表中的列名时,才需要将c1限定为fdt.c1。但是,限定列名即使在不需要时也可以增加清晰度。本示例说明外部查询的列命名范围如何扩展到其内部查询。

7 .2.3. GROUP BY 和 HAVING 子句

通过WHERE过滤器后,派生的 Importing 表可能会使用GROUP BY子句进行分组,并使用HAVING子句消除组行。

SELECT select_list
    FROM ...
    [WHERE ...]
    GROUP BY grouping_column_reference [, grouping_column_reference]...

按条款分组用于将表中所有列出的列中具有相同值的行组合在一起。列的列出 Sequences 无关紧要。效果是将具有公共值的每组行合并为一个代表组中所有行的组行。这样做是为了消除输出和/或计算应用于这些组的聚合中的冗余。例如:

=> 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.20

Tip

没有聚合表达式的分组有效地计算了列中不同值的集合。这也可以使用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_idp.namep.price必须在GROUP BY子句中,因为它们在查询选择列表中被引用(但请参见下文)。列s.units不必在GROUP BY列表中,因为它仅用于表示产品销售的聚合表达式(sum(...))中。对于每种产品,查询将返回有关该产品所有销售额的摘要行。

如果将产品表设置为例如product_id是主键,则在上面的示例中按product_id分组就足够了,因为名称和价格在功能上取决于产品 ID,因此对于每个产品 ID 组返回哪个名称和价格值都不会有任何歧义。

在严格的 SQL 中,GROUP BY只能按源表的列进行分组,但是 PostgreSQL 对此进行了扩展,以允许GROUP BY在选择列表中按列进行分组。也允许按值表达式而不是简单的列名分组。

如果已使用GROUP BY对表进行了分组,但仅关注某些组,则可以使用HAVING子句(与WHERE子句非常相似)从结果中消除组。语法为:

SELECT select_list FROM ... [WHERE ...] GROUP BY ... HAVING boolean_expression

HAVING子句中的表达式既可以引用已分组的表达式,也可以引用未分组的表达式(它们必须涉及聚合函数)。

Example:

=> 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子句,也是如此。

7 .2.4. 分组集,多维数据集和汇总

使用分组集的概念,可以实现比上述更复杂的分组操作。 FROMWHERE子句选择的数据按每个指定的分组集分别分组,为每个组计算的汇总与简单的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子句中相同的方式进行解释。空分组集意味着将所有行聚合为一个组(即使不存在 Importing 行也将被输出),如上文针对不具有GROUP BY子句的聚合函数的情况所述。

在没有出现这些列的分组集的结果行中,对分组列或表达式的引用将替换为空值。要区分特定输出行来自哪个分组,请参见Table 9.56

提供了一种速记符号,用于指定两种常见的分组集类型。形式的子句

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 ),
    (         )
)

CUBEROLLUP子句的各个元素可以是单个表达式,也可以是括号中的元素的子列表。在后一种情况下,子列表被视为单个单元,以生成单独的分组集。例如:

CUBE ( (a, b), (c, d) )

相当于

GROUPING SETS (
    ( a, b, c, d ),
    ( a, b       ),
    (       c, d ),
    (            )
)

and

ROLLUP ( a, (b, c), d )

相当于

GROUPING SETS (
    ( a, b, c, d ),
    ( a, b, c    ),
    ( a          ),
    (            )
)

CUBEROLLUP构造既可以直接在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)
)

Note

构造(a, b)通常在表达式中识别为row constructor。在GROUP BY子句中,这不适用于表达式的最高级别,并且如上所述,(a, b)被解析为表达式列表。如果出于某种原因需要分组表达式中的行构造器,请使用ROW(a, b)

7 .2.5. 窗口函数处理

如果查询包含任何窗口函数(请参见Section 3.5Section 9.21Section 4.2.8),则在执行任何分组,聚合和HAVING过滤后将评估这些函数。也就是说,如果查询使用GROUP BYHAVING的任何聚合,则窗口函数看到的行是组行,而不是FROM/WHERE的原始表行。

当使用多个窗口函数时,保证在其窗口定义中具有句法上等效的PARTITION BYORDER BY子句的所有窗口函数都可以通过一次数据传递来评估。因此,即使ORDER BY不能唯一地确定 Sequences,他们也将看到相同的排序 Sequences。但是,不能保证对具有不同PARTITION BYORDER BY规格的功能进行评估。 (在这种情况下,通常在传递窗口函数评估之间需要一个排序步骤,并且不能保证该排序保留其ORDER BY视为等效的行的 Sequences.)

当前,窗口函数始终需要预先排序的数据,因此查询输出将根据窗口函数的PARTITION BY/ORDER BY子句中的一个或另一个进行排序。但是,不建议依赖于此。如果要确保结果以特定方式排序,请使用显式的ORDER BY子句。