PostgreSQL之查询
表连接
表t1:
num | name -----+------ 1 | a 2 | b 3 | c
表t2:
num | value -----+------- 1 | xxx 3 | yyy 5 | zzz
1.交叉连接
T1 CROSS JOIN T2
对来自于T1
和T2
的行的每一种可能的组合(即笛卡尔积),连接表将包含这样一行:它由所有T1
里面的列后面跟着所有T2
里面的列构成。如果两个表分别有 N 和 M 行,连接表将有 N * M 行。
FROM
等效于T1
CROSS JOIN T2
FROM
(见下文)。它也等效于T1
INNER JOIN T2
ON TRUEFROM
。T1
,T2
=> 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)
2.条件连接
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
隐含地指定。连接条件决定来自两个源表中的哪些行是“匹配”的。
2.1 INNER JOIN
对于 T1 的每一行 R1,生成的连接表都有一行对应 T2 中的每一个满足和 R1 的连接条件的行。
=> SELECT * FROM t1 INNER JOIN t2 ON t1.num = t2.num; num | name | num | value -----+------+-----+------- 1 | a | 1 | xxx 3 | c | 3 | yyy
2.2 LEFT OUTER JOIN
列用空值补齐。因此,生成的连接表里为来自 T1 的每一行都至少包含一行。首先,执行一次内连接。然后,为 T1 中每一个无法在连接条件上匹配 T2 里任何一行的行返回一个连接行,该连接行中 T2 的列用空值补齐。因此,生成的连接表里为来自 T1 的每一行都至少包含一行。
=> 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
2.3 RIGHT OUTER JOIN
首先,执行一次内连接。然后,为 T2 中每一个无法在连接条件上匹配 T1 里任何一行的行返回一个连接行,该连接行中 T1 的列用空值补齐。因此,生成的连接表里为来自 T2 的每一行都至少包含一行。
=> 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)
2.4 FULL OUTER JOIN
首先,执行一次内连接。然后,为 T1 中每一个无法在连接条件上匹配 T2 里任何一行的行返回一个连接行,该连接行中 T2 的列用空值补齐。同样,为 T2 中每一个无法在连接条件上匹配 T1 里任何一行的行返回一个连接行,该连接行中 T1 的列用空值补齐。
=> 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
VALUES
列表
VALUES
提供了一种生成“常量表”的方法,它可以被使用在一个查询中而不需要实际在磁盘上创建一个表。
VALUES ( expression [, ...] ) [, ...]
=> SELECT * FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) AS t (num,letter); num | letter -----+-------- 1 | one 2 | two 3 | three (3 rows)
从修改的行中返回数据
在INSERT
中,可用于RETURNING
的数据是插入的行。
CREATE TABLE users (firstname text, lastname text, id serial primary key); INSERT INTO users (firstname, lastname) VALUES ('Joe', 'Cool') RETURNING id;
在UPDATE
中,可用于RETURNING
的数据是被修改行的新内容。
UPDATE products SET price = price * 1.10 WHERE price <= 99.99 RETURNING name, price AS new_price;
在DELETE
中,可用于RETURNING
的数据是删除行的内容。
DELETE FROM products WHERE obsoletion_date = 'today' RETURNING *;
WITH查询
WITH
提供了一种方式来书写在一个大型查询中使用的辅助语句。这些语句通常被称为公共表表达式或CTE,它们可以被看成是定义只在一个查询中存在的临时表。在WITH
子句中的每一个辅助语句可以是一个SELECT
、INSERT
、UPDATE
或DELETE
,并且WITH
子句本身也可以被附加到一个主语句,主语句也可以是SELECT
、INSERT
、UPDATE
或DELETE
。
1. WITH中的SELECT
WITH
中SELECT
的基本价值是将复杂的查询分解称为简单的部分。
WITH regional_sales AS ( SELECT region, SUM(amount) AS total_sales FROM orders GROUP BY region ), top_regions AS ( SELECT region FROM regional_sales WHERE total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales) ) SELECT region, product, SUM(quantity) AS product_units, SUM(amount) AS product_sales FROM orders WHERE region IN (SELECT region FROM top_regions) GROUP BY region, product;
可选的RECURSIVE
修饰符将WITH
从单纯的句法便利变成了一种在标准SQL中不能完成的特性。通过使用RECURSIVE
,一个WITH
查询可以引用它自己的输出。
WITH RECURSIVE search_graph(id, link, data, depth) AS ( SELECT g.id, g.link, g.data, 1 FROM graph g UNION ALL SELECT g.id, g.link, g.data, sg.depth + 1 FROM graph g, search_graph sg WHERE g.id = sg.link ) SELECT * FROM search_graph;
WITH
查询的一个有用的特性是在每一次父查询的执行中它们通常只被计算一次,即使它们被父查询或兄弟WITH
查询引用了超过一次。 因此,在多个地方需要的昂贵计算可以被放在一个WITH
查询中来避免冗余工作。
如果 WITH
查询是非递归和边际效应无关的(就是说,它是一个SELECT
包含没有可变函数),则它可以合并到父查询中,允许两个查询级别的联合优化。 默认情况下,这发生在如果父查询仅引用 WITH
查询一次的时候,而不是它引用WITH
查询多于一次时。
WITH w AS ( SELECT * FROM big_table ) SELECT * FROM w WHERE key = 123;
这个 WITH
查询将被合并,生成相同的执行计划为
SELECT * FROM big_table WHERE key = 123;
2.WITH
中的数据修改
在WITH
中使用数据修改语句(INSERT
、UPDATE
或DELETE
),这允许你在同一个查询中执行多个而不同操作。
WITH moved_rows AS ( DELETE FROM products WHERE "date" >= '2010-10-01' AND "date" < '2010-11-01' RETURNING * ) INSERT INTO products_log SELECT * FROM moved_rows;
这个查询实际上从products
把行移动到products_log
。WITH
中的DELETE
删除来自products
的指定行,以它的RETURNING
子句返回它们的内容,并且接着主查询读该输出并将它插入到products_log
。
3.WITH中的MATERIALIZED
WITH w AS ( SELECT * FROM big_table ) SELECT * FROM w AS w1 JOIN w AS w2 ON w1.key = w2.ref WHERE w2.key = 123;
WITH
查询将被物化,生成一个big_table
的临时拷贝,然后与其自身 — 联合,这样将不能从索引上获得任何好处。 如果写成下面的形式,这个查询将被执行得更有效率。
WITH w AS NOT MATERIALIZED ( SELECT * FROM big_table ) SELECT * FROM w AS w1 JOIN w AS w2 ON w1.key = w2.ref WHERE w2.key = 123;
所以父查询的限制可以直接应用于big_table
的扫描。
一个NOT MATERIALIZED
可能不理想的例子为:
WITH w AS ( SELECT key, very_expensive_function(val) as f FROM some_table ) SELECT * FROM w AS w1 JOIN w AS w2 ON w1.f = w2.f;
在这里,WITH
查询的物化确保very_expensive_function
每个表行只计算一次,而不是两次。