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

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 8.2 已于 2011 年 12 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本。

7.2. 表表达式 #

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

表表达式中可选的WHERE、GROUP BY和HAVING子句指定了一个连续转换的流水线,这些转换作用于由FROM子句派生出的表。所有这些转换都会生成一个虚拟表,该表提供的各行会传递给选择列表,以计算查询的输出行。

7.2.1. FROM子句 #

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

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

表引用可以是表名(可能带模式限定),也可以是子查询、表连接等推导表,或它们的复杂组合。如果FROM子句中列出了多个表引用,它们会被交叉连接(见下文)以形成中间虚拟表,随后该表可以用WHERE, GROUP BY和HAVING子句对它进行变换,最终得到整个表表达式的结果。

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

7.2.1.1. 连接表 #

一个连接表是根据特定的连接类型的规则从两个其它表(真实表或生成表)中派生的表。目前支持内连接、外连接和交叉连接。

连接类型

Cross join
T1 CROSS JOIN T2

对T1的每一行与T2的每一行的组合,派生表将包含这样一行:它由所有T1里面的列后面跟着所有T2里面的列构成。如果两个表分别有 N 和 M 行,连接表将有 N * M 行。

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

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表示外连接。

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

ON子句是最通用的连接条件形式:它接受一个与WHERE子句中所用种类相同的布尔值表达式。如果一对分别来自T1和T2的行在ON表达式上的求值结果为真,那么它们就是匹配的。

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一样,这些列在输出表中只出现一次。

限定连接有以下几种类型:

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 的列用空值补齐。

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

综合起来看,假设我们有表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)

7.2.1.2. 表和列别名 #

你可以给表以及复杂的表引用指定一个临时名字,以便在查询的其余部分引用该派生表。这被称为表别名。

要创建一个表别名,我们可以写:

FROM table_reference AS alias

或者

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;

按照 SQL 标准这是无效的。在PostgreSQL中,假定add_missing_from配置变量为off(默认即是如此),这将引发一个错误。如果它为on,系统会向FROM子句隐式添加一个表引用,因此该查询会像下面这样写一样被处理:

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

这将导致一个交叉连接,而这通常不是你想要的。

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

SELECT * FROM people AS mother JOIN people AS child ON mother.id = child.mother_id;

此外,当表引用是子查询时也必须使用别名(参见第 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. 子查询 #

指定派生表的子查询必须用圆括号括起来,并且必须赋予一个表别名。(参见第 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 节。

7.2.1.4. 表函数 #

表函数是那些生成行集合的函数,这些行可以由基础类型(标量类型)组成,也可以由复合数据类型(表行)组成。它们在查询的FROM子句中用法类似于表、视图或子查询。表函数返回的列可以像表、视图或子查询的列一样,出现在SELECT、JOIN或WHERE子句中。

如果一个表函数返回基础数据类型,唯一的结果列名与该函数名相同。如果该函数返回一个复合类型,结果列会得到与该类型的各个属性相同的名称。

表函数可以在FROM子句中被赋予别名,但也可以不使用别名。如果一个函数在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。当此类函数在查询中使用时,必须在查询本身中指定预期的行结构,这样系统才能知道如何分析和规划该查询。考虑下面的示例:

SELECT *
    FROM dblink('dbname=mydb', 'SELECT proname, prosrc FROM pg_proc')
      AS t1(proname name, prosrc text)
    WHERE proname LIKE 'bytea%';

dblink函数执行一个远程查询(见contrib/dblink)。它被声明为返回record,因为它可能用于任意类型的查询。实际的列集必须在调用它的查询中指定,这样分析器才知道像*这样的写法应当扩展成什么。

7.2.2. WHERE子句 #

此WHERE Clause的语法是:

WHERE search_condition

其中,search_condition可以是任意值表达式(见第 4.2 节),只要其返回值的类型为boolean。

在完成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仅在c1也是子查询推导输入表中的列名时才有必要。不过,即使不必限定,限定列名仍能使含义更清晰。本例说明了外层查询的列命名作用域如何扩展到其内部查询。

7.2.3. GROUP BY和HAVING子句 #

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

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

此GROUP BY Clause用于把表中所有列出列的值均相同的行分到同一组。列的列出顺序没有影响。其效果是把具有相同值的每一组行合并为一个分组行,由它代表组内的所有行。这样可以消除输出中的冗余,或者计算应用于各组的聚合。例如:

=> 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 x, sum(y) FROM test1 GROUP BY x;
 x | sum
---+-----
 a |   4
 b |   5
 c |   2
(3 rows)

这里的sum是一个聚合函数,它在整个组上计算出一个单一值。有关可用的聚合函数的更多信息可以在第 9.15 节中找到。

提示

不带聚合表达式的分组实际上是在计算某一列的非重复值集合。这也可以用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子句里,因为它们都在查询的选择列表里被引用到。(根据 products 表的具体设计,name 和 price 可能完全依赖于产品 ID,因此理论上这些额外的分组并非必要,不过这一点尚未实现。)列s.units不必在GROUP BY列表里,因为它只是在一个聚合表达式(sum(...))里使用,它代表一种产品的销售额。对于每种产品,这个查询都返回一行关于该产品所有销售额的汇总。

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

如果一个表已经用GROUP BY子句分了组,而你只对其中某些组感兴趣,那么就可以使用HAVING子句。它很像WHERE子句,用于从结果中删除某些组。其语法是:

SELECT select_list FROM ... [WHERE ...] GROUP BY ... HAVING boolean_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 的组。请注意,聚合表达式不必在查询的所有部分都完全相同。

提交更正

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