↑↓ 选择 ↵ 打开 ⌫ 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2
历史版本PostgreSQL 7.2 已于 2007 年 2 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本。

2.2. 表表达式 #

表表达式指定一个表。表表达式包含一个 FROM 子句,后面可以根据需要跟上 WHERE、GROUP BY 和 HAVING 子句。最简单的表表达式只是引用磁盘上的一个表,即所谓的基本表;但也可以使用更复杂的表达式,以多种方式修改或组合基本表。

表表达式中可选的 WHERE、GROUP BY 和 HAVING 子句指定了一个连续变换的流水线,这些变换作用于由 FROM 子句派生出的表。所有这些变换所产生的派生表提供输入行,用于按选择列表中的列值表达式计算输出行。

2.2.1. FROM 子句 #

FROM 子句从逗号分隔的表引用列表所给出的一个或多个其他表推导出一个表。

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

表引用可以是表名,也可以是子查询、表连接等推导表,或它们的复杂组合。如果 FROM 子句中列出了多个表引用,这些表会进行交叉连接(见下文),形成派生表,随后该派生表可以被 WHERE、GROUP BY 和 HAVING 子句变换,并最终成为整个表表达式的结果。

当表引用命名的是表继承层次中的父表时,除非在表名前加上关键字 ONLY,否则该表引用不仅会产生该表中的行,还会产生其所有子表后代的行。不过,这种引用只会产生所命名表中出现的列 ——子表中新增的列会被忽略。

2.2.1.1. 连接表 #

一个连接表是根据特定的连接类型的规则从两个其它表(真实表或推导表)中派生的表。支持 INNER、OUTER 和 CROSS JOIN。

连接类型

CROSS JOIN
T1 CROSS JOIN T2

对来自于T1和T2的行的每一种组合,派生表将包含这样一行:它由所有T1中的列后面跟着所有T2中的列构成。如果两个表分别有 N 和 M 行,连接表将有 N * M 行。交叉连接等效于 INNER JOIN ON TRUE。

提示

FROM T1 CROSS JOIN T2等效于 FROM T1, T2。

Qualified joins
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

INNER和OUTER对所有连接都是可选的。INNER是默认值;LEFT、RIGHT和FULL表示 OUTER JOIN。

连接条件在 ON 或 USING 子句中指定,或者用关键字 NATURAL 隐含地指定。连接条件决定来自两个源表中的哪些行被认为是“匹配”的,这些我们将在下文详细解释。

ON 子句是最一般的连接条件形式:它接收一个和 WHERE 子句里用的一样的布尔值表达式。如果两个分别来自 T1 和 T2 的行在 ON 表达式上运算的结果为 TRUE,那么它们就算是匹配的行。

USING 是个缩写符号:它接受一个逗号分隔的列名列表,这些列名是被连接的两个表必须共有的,并为其中每一对列构造一个指定等值比较的连接条件。此外,JOIN USING 的输出为每一对相等的输入列产生一个列,后面跟上各个表的所有其余列。因此,USING (a, b, c)等效于ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c),不同之处在于:如果使用 ON,结果中会有两个分别名为 a、b 和 c 的列,而使用 USING 时每列只有一个。

最后,NATURAL 是 USING 的一种缩写形式:它会形成一个 USING 列表,其中包含的正是两个输入表中共同出现的那些列名。和 USING 一样,这些列在输出表中只出现一次。

限定 JOIN 有以下几种类型:

INNER JOIN

对于 T1 的每一行 R1,连接表都有一行对应 T2 中的每一个满足和 R1 的连接条件的行。

LEFT OUTER JOIN

首先,执行一次 INNER JOIN。然后,为 T1 中每一个无法在连接条件上匹配 T2 里任何一行的行返回一个连接行,该连接行中 T2 的列用 NULL 值补齐。因此,连接表里为来自 T1 的每一行都无条件地至少包含一行。

RIGHT OUTER JOIN

首先,执行一次 INNER JOIN。然后,为 T2 中每一个无法在连接条件上匹配 T1 里任何一行的行返回一个连接行,该连接行中 T1 的列用 NULL 值补齐。这是左连接的反面:结果表里为来自 T2 的每一行都无条件地包含一行。

FULL OUTER JOIN

首先,执行一次 INNER JOIN。然后,为 T1 中每一个无法在连接条件上匹配 T2 里任何一行的行返回一个连接行,该连接行中 T2 的列用空值补齐。同样,为 T2 中每一个无法在连接条件上匹配 T1 里任何一行的行返回一个连接行,该连接行中 T1 的列用空值补齐。

所有类型的连接都可以被链在一起或者嵌套:T1和T2都可以是连接表。可以在 JOIN 子句周围使用圆括号来控制连接顺序。如果不使用圆括号,JOIN 子句会从左至右嵌套。

2.2.1.2. 子查询 #

指定派生表的子查询必须用圆括号括起来,并且必须用 AS 子句命名。(参见第 2.2.1.3 节。)

FROM (SELECT * FROM table1) AS alias_name

这个示例等效于FROM table1 AS alias_name。更有趣的情况是在子查询里面有分组或聚合的时候,这时子查询不能被简化为一个简单的连接。

2.2.1.3. 表和列别名 #

你可以给表以及复杂的表引用指定一个临时名字,以便在后续处理中引用该派生表。这被称为表别名。

FROM table_reference AS alias

这里,alias可以是任意常规标识符。对于当前查询而言,别名成为该表引用的新名称——不再能通过原名称引用该表。因此

SELECT * FROM my_table AS m WHERE my_table.a > 5;

不是合法的 SQL 语法。实际会发生的是(这是PostgreSQL对标准的一个扩展):一个隐式的表引用会被加入 FROM 子句,因此该查询会被当作像下面这样写的一样来处理

SELECT * FROM my_table AS m, my_table AS my_table WHERE my_table.a > 5;

表别名主要用于简化符号,但是当把一个表连接到它自身时必须使用别名,例如:

SELECT * FROM my_table AS a CROSS JOIN my_table AS b ...

此外,当表引用是子查询时也必须使用别名。

圆括弧用于解决歧义。下面的语句会把别名b赋给连接的结果,这与前一个例子不同:

SELECT * FROM (my_table AS a CROSS JOIN my_table) AS b ...
FROM table_reference alias

这种形式与前面处理的那种等价;AS关键字只是可选的语法噪音。

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 外面是看不到的。

2.2.1.4. 例子 #

FROM T1 INNER JOIN T2 USING (C)
FROM T1 LEFT OUTER JOIN T2 USING (C)
FROM (T1 RIGHT OUTER JOIN T2 ON (T1.C1=T2.C1)) AS DT1
FROM (T1 FULL OUTER JOIN T2 USING (C)) AS DT1 (DT1C1, DT1C2)

FROM T1 NATURAL INNER JOIN T2
FROM T1 NATURAL LEFT OUTER JOIN T2
FROM T1 NATURAL RIGHT OUTER JOIN T2
FROM T1 NATURAL FULL OUTER JOIN T2

FROM (SELECT * FROM T1) DT1 CROSS JOIN T2, T3
FROM (SELECT * FROM T1) DT1, T2, T3

上面是一些连接表和复杂派生表的例子。注意 AS 子句如何重命名或命名派生表,以及其后可选的逗号分隔列名列表如何重命名列。最后两个 FROM 子句从 T1、T2 和 T3 产生相同的派生表。在把子查询命名为 DT1 时省略了 AS 关键字。关键字 OUTER 和 INNER 也是可以省略的噪音。

2.2.2. WHERE 子句 #

WHERE 子句的语法是

WHERE search_condition

其中,search_condition是任意的值表达式(如第 1.3 节中所定义),它返回一个boolean类型的值。

在完成 FROM 子句的处理之后,派生表中的每一行都会根据搜索条件进行检查。如果条件结果为真,该行就会保留在输出表中;否则(也就是说,如果结果为假或 NULL)就会被丢弃。搜索条件通常至少会引用 FROM 子句生成的表中的某一列;这并非必须,但若不引用这些列,WHERE 子句通常就没什么用了。

注意

在 JOIN 语法实现之前,内连接的连接条件必须放在 WHERE 子句中。例如,这些表表达式是等效的:

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 语法移植到其他产品时可能没有那么容易。对于外连接在任何情况下都没有选择的余地:它们必须在 FROM 子句中完成。外连接的 A ON/USING 子句与 WHERE 条件并不等价,因为它除了会在最终结果中移除行之外,还会为不匹配的输入行增加结果行。

FROM FDT WHERE
    C1 > 5

FROM FDT WHERE
    C1 IN (1, 2, 3)
FROM FDT WHERE
    C1 IN (SELECT C1 FROM T2)
FROM FDT WHERE
    C1 IN (SELECT C3 FROM T2 WHERE C2 = FDT.C1 + 10)

FROM FDT WHERE
    C1 BETWEEN (SELECT C3 FROM T2 WHERE C2 = FDT.C1 + 10) AND 100

FROM FDT WHERE
    EXISTS (SELECT C1 FROM T2 WHERE C2 > FDT.C1)

在上面的例子中,FDT是由 FROM 子句推导得到的表。不满足 where 子句搜索条件的行会从FDT中消除。注意,这里将标量子查询用作值表达式。与其他查询一样,子查询也可以使用复杂的表表达式。还要注意子查询中对FDT的引用方式。将C1限定为FDT.C1仅在C1也是子查询推导输入表中的列名时才有必要。限定列名即使在不必要的情况下也能增加清晰度。这说明了外层查询的列命名作用域如何扩展到其内部查询。

2.2.3. GROUP BY 和 HAVING 子句 #

通过 WHERE 过滤之后,派生出来的输入表还可以使用 GROUP BY 子句进行分组,并通过 HAVING 子句排除某些分组行。

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

GROUP BY 子句用于把表中所有列出列的值均相同的行分到同一组。列的列出顺序没有影响(这与 ORDER BY 子句不同)。其效果是把具有相同值的每一组行合并为一个分组行,由它代表组内的所有行。这样可以消除输出中的冗余,或者获得应用于各组的聚合。

一旦一个表被分组,没有参与分组的列就不能被引用,除非是在聚合表达式中,因为那些列中的特定值是有歧义的——它应该来自组中的哪一行?参与分组的列可以在选择列表的列表达式中引用,因为它们在每个组都有已知的常量值。对未分组列的聚合函数提供的是跨一个组的各行(而不是整张表)的值。例如,在按产品代码分组的表上,sum(sales)给出每种产品的总销售额,而不是所有产品的总销售额。在未分组列上计算的聚合代表的是组,而未分组列的单个值则不代表组。

例子:

SELECT pid, p.name, (sum(s.units) * p.price) AS sales
  FROM products p LEFT JOIN sales s USING ( pid )
  GROUP BY pid, p.name, p.price;

在这个示例里,列pid、p.name和p.price必须在 GROUP BY 子句里,因为它们都在查询的选择列表里被引用到。列 s.units 不必在 GROUP BY 列表里,因为它只是在一个聚合表达式(sum())里使用,它代表一种产品的销售额。对于每种产品,这个查询都返回一个关于该产品所有销售额的汇总行。

在严格的 SQL 里,GROUP BY 只能对源表的列进行分组,但PostgreSQL把这个扩展为也允许 GROUP BY 去根据查询选择列表中的列分组。也允许对值表达式进行分组,而不仅是简单的列名。

如果一个表已经用 GROUP BY 子句分了组,而你只对其中某些组感兴趣,那么就可以使用 HAVING 子句。它很像 WHERE 子句,用于从分组表中删除某些组。PostgreSQL允许 HAVING 子句在没有 GROUP BY 的情况下使用,这时它的作用就像另一个 WHERE 子句,但这样使用 HAVING 的意义并不明确。一个好的经验法则是 HAVING 条件应该引用聚合函数的结果。不涉及聚合的限制条件用 WHERE 子句表达更高效。其语法是

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

一个更实际的例子:

SELECT pid    AS "Products",
       p.name AS "Over 5000",
       (sum(s.units) * (p.price - p.cost)) AS "Past Month Profit"
  FROM products p LEFT JOIN sales s USING ( pid )
  WHERE s.date > CURRENT_DATE - INTERVAL '4 weeks'
  GROUP BY pid, p.name, p.price, p.cost
    HAVING sum(p.price * s.units) > 5000;

在上面的示例里,WHERE 子句通过一个未分组的列来选择数据行,而 HAVING 子句则把输出限制为总销售收入超过 5000 的组。

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。