表表达式里的 WHERE,GROUP BY,和 HAVING 子句声明一系列对源自 FROM 子句的表的转换操作.转换最后生成的表提供用于产生输出行的 输入,这些输出行都是在选择列表的列表达式中生成的.
FROM 子句从一个用逗号分隔的表引用列表中的一个或更多个其它表 中生成一个表.
FROM table_reference [, table_reference [, ...]]
表引用可以是一个表名字或者是一个生成的表,比如子查询,一个 表连接,或者这些东西的复杂组合.如果在 FROM 子句中列出了多于一个表, 那么它们被 CROSS JOIN (见下文)形成一个派生表,该表可以进行 WHERE,GROUP BY 和 HAVING 子句的转换处理,并最后生成所有表表达式的结果.
如果一个表引用是一个简单的表名字并且它是表继承级别中的 超级表,那么该表的行包括所有它的后代子表的行,除非你在 该表名字前面加 ONLY 关键字.这样的话,这个引用就只生成 出现在命名表中的列 --- 任何在子表中追加的列都会被忽略.
一个连接表是根据特定的连接类型的规则从两个其它表(真实表或生成表) 中排生的表.我们支持 INNER,OUTER,和 CROSS JOIN 类型.
连接类型
- CROSS JOIN
T1 CROSS JOIN T2
对每个从 T1 和 T2 来的行的组合, 生成的表将包含这样一行:它包含所有 T1 里面的列后面跟着所有 T2 里面的列. 如果该表分别有 N 和 M 行,连接成的表将有 N * M 行.一次 cross join (交叉连接)实际上是一个 INNER JOIN ON TRUE.
技巧: FROM T1 CROSS JOIN T2 等效于 FROM T1, T2.
- 条件 JOIN
T1 { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2 ON boolean_expressionT1 { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2 USING ( join column list )
T1 NATURAL { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2
INNER 和 OUTER 对所有连接(JOIN) 类型都是可选的.INNER 是缺省; LEFT,RIGHT,和 FULL 只用于 OUTER JOIN.
连接条件在 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 一样, 这些字段只在输出表里出现一次.
条件 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 的列用 NULL 补齐. 同样,为 T2 里那些和 T1 里的任何行都不满足连接条件的 行返回一个连接行,该行里对应 T1 的列用 NULL 补齐.
如果 T1 和 T2 有一个或者都是可以 JOIN 的表, 那么所有类型的连接都可以串在一起或嵌套在一起. 你可以在JOIN子句周围使用圆括弧来控制连接顺序, 如果没有圆括弧,那么 JOIN 子句是从左向右嵌套的.
声明一个派生表的子查询必须包围在圆括弧里并且必须 用 AS 子句命名.(参阅 Section 2.1.1.3.)
FROM (SELECT * FROM table1) AS alias_name
这个例子等效于 FROM table1 AS alias_name. 更有趣的例子是在子查询里面有分组或聚集的时候, 这个时候子查询不能归纳成一个简单的连接.
你可以给一个表或复杂表引用一个临时的名字,用于在后面的 处理过程中引用那些派生的表.这样做叫做 表别名.
FROM table_reference AS alias
在这里,
alias 可以是任何规则的标识符. 这个别名成为当前查询里该表引用的新名字 -- 同时我们也不能再用原来的 名字引用该表了(如果该表引用是一个普通的基本表).因此
SELECT * FROM my_table AS m WHERE my_table.a > 5;
是非法的 SQL 语法.作为
Postgres 对标准的扩展,这里实际发生的事情是那个隐含的表引用加入到 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_referencealias
这个形式等效于前面那个;
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 外面是看不到的.
FROM T1 INNER JOIN T2 USING (C)
FROM T1 LEFT OUTER JOIN T2 USING (C)
FROM (T1 RIGHT OUTER JOIN T2 ON (T1C1=T2C1)) 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 都是摆设,也可以省略.
WHERE 子句的语法是
WHERE search_condition
这里的
search condition 是定义在
Section 1.3 里的任意表达式,它返回一个 类型为
boolean的值.
在完成对 FROM 子句的处理之后,生成的每一行都会对搜索条件进行检查. 如果该条件的结果是真,那么该行输出到输出表中,否则(也就是说, 如果结果是假或 NULL)就把它抛弃.搜索条件通常至少要引用一些在 FROM 子句里生成的列;这不是必须的,但如果不是这样的话,那么 WHERE 子句就没什么用了.
注意: 在 JOIN 语法实现以前,我们必须把 inner 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 语法可能不那么 容易移植到其它产品中.对于外部连接(outer join)而言,我们在任何 情况下都没有选择:它们必须在 FROM 子句中完成.外部连接的 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 中删除.请注意我们把标量 子查询当做一个值表达式来用(假设 C2 UNIQUE (唯一)). 就好象任何其它查询一样,子查询里可以使用复杂的表表达式. 请注意 FDT 是如何引用子查询的.把 C1 修饰成 FDT.C1 只有 在 C1 是该子查询生成的列的名字时才是必须的. 修饰列名字可以增加语句的准确性,即使有时候不是必须的. 列名字的作用范围从外层查询扩展到它的内层查询.
在通过了 WHERE 过滤器之后,生成的输出表可以继续用 GROUP BY 子句进行分组,然后用 HAVING 子句删除一些分组行.
SELECT select_list FROM ... [WHERE ...] GROUP BY grouping_column_reference [, grouping_column_reference]...
GROUP BY 子句用于把一个表中在所列出的列上共享相同值的行聚集在一起. 这些列的列出顺序如果并没有什么关系(和 ORDER BY 子句相反). 目的是把每组共享相同值的行缩减为一个组行,它代表该组里的所有行. 这样就可以删除输出里的重复和/或获取应用于这些组的聚集.
一旦对一个表分了组,那么没有包含在分组中的列就不能引用, 除了在一个聚集表达式中以外,因为在那些列中的一个特定值是 模糊的 - 它应该来自哪个分组的行?我们可以在一个选择列表 的列表达式中引用 group-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 只能对源表的列进行分组,但 Postgres 把这个扩展为也允许 GROUP BY 那些在选择列表中的选则列.也允许 对值表达式进行分组,而不仅是简单的列.
SELECT select_list FROM ... [WHERE ...] GROUP BY ... HAVING boolean_expression
如果一个表已经用 GROUP BY 子句分了组,然后你又只对其中的某些 组感兴趣,那么就可以用 HAVING 子句,很象 WHERE 子句,以删除 一个分了组的表中的一些组.对于一些查询,Postgres 允许不带 GROUP BY 子句使用 HAVING 子句,这时候它的作用就象另外一个 WHERE 子句,但是那么用 HAVING 的目的并不清晰.因为 HAVING 对组进行操作,只有分组的列可以出现在 HAVING 子句里.如果一些 基于非分组的列需要进行处理,那么它们应该出现在 WHERE 子句中.
例子:
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 的组的行.