pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
表表达式用于计算出一个表。表表达式包含一个FROM子句,后面可以根据需要跟上WHERE、GROUP BY和HAVING子句。最简单的表表达式只是引用磁盘上的一个表,即所谓的基本表;但也可以使用更复杂的表达式,以多种方式修改或组合基本表。
表表达式中可选的WHERE、GROUP BY和HAVING子句指定了一个连续转换的流水线,这些转换作用于由FROM子句派生出的表。所有这些转换都会生成一个虚拟表,该表提供的各行会传递给选择列表,以计算查询的输出行。
FROM 子句 #FROM子句从逗号分隔的表引用列表所给出的一个或多个其他表推导出一个表。
FROMtable_reference[,table_reference[, ...]]
表引用可以是表名(可能带模式限定),也可以是推导表,例如子查询、表连接,或它们的复杂组合。如果FROM子句中列出了多个表引用,这些表会进行交叉连接(见下文),形成一个中间虚拟表,随后该虚拟表可以被WHERE、GROUP BY和HAVING子句变换,并最终成为整个表表达式的结果。
当表引用命名的是表继承层次中的父表时,除非在表名前加上关键字ONLY,否则该表引用不仅会产生该表中的行,还会产生其所有子表后代的行。不过,这种引用只会产生所命名表中出现的列——子表中新增的列会被忽略。
一个连接表是根据特定的连接类型的规则从两个其它表(真实表或推导表)中派生的表。目前可以使用内连接、外连接和交叉连接。
连接类型
T1CROSS JOINT2
对来自于T1和T2的行的每一种组合,派生表将包含这样一行:它由所有T1中的列后面跟着所有T2中的列构成。如果两个表分别有 N 和 M 行,连接表将有 N * M 行。
FROM 等效于T1 CROSS JOIN T2FROM 。它也等效于 T1, T2FROM (见下文)。T1 INNER JOIN T2 ON TRUE
T1{ [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOINT2ONboolean_expressionT1{ [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOINT2USING (join column list)T1NATURAL { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOINT2
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)
你可以给表以及复杂的表引用指定一个临时名字,以便在后续处理中引用该派生表。这被称为表别名。
要创建一个表别名,我们可以写:
FROMtable_referenceASalias
或者
FROMtable_referencealias
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对标准的一个扩展):一个隐式的表引用会被加入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 ...
此外,当表引用是子查询时也必须使用别名(参见第 7.2.1.3 节)。
圆括弧用于解决歧义。下面的语句会把别名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。更有趣的情况是在子查询里面有分组或聚合的时候,这时子查询不能被简化为一个简单的连接。
表函数是那些生成行集合的函数,这些行可以由基础数据类型(标量类型)组成,也可以由复合数据类型(表行)组成。它们在查询的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,因为它可能用于任意类型的查询。实际的列集必须在调用它的查询中指定,这样分析器才知道像*这样的写法应当扩展成什么。
WHERE 子句 #WHERE子句的语法是:
WHERE search_condition
其中,search_condition是任意的值表达式(如第 4.2 节中所定义),它返回一个boolean类型的值。
在完成FROM子句的处理之后,生成的虚拟表中的每一行都会根据搜索条件进行检查。如果条件结果为真,该行就会保留在输出表中;否则(也就是说,如果结果为假或空)就会被丢弃。搜索条件通常至少会引用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语法移植到其他 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_listFROM ... [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 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子句,用于从结果中删除某些组。其语法是:
SELECTselect_listFROM ... [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 的组。请注意,聚合表达式不必在查询的所有部分都完全相同。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。