with語句的用法
with查詢在複雜查詢中定義一個輔助語句(可以理解爲在一個查詢中定義的臨時表),這一特性常用語複雜查詢或遞歸查詢應用場景
1.在複雜查詢語句中
mydb@localhost:5432=>with t as
mydb-> (select generate_series(1, 3))
mydb-> select * from t;
generate_series
-----------------
1
2
3
(3 rows)
一開始定義了一條輔助語句取數,之後在主查詢語句中查詢t,定義的輔助語句就像是定義了一張臨時表,對於複雜查詢如果不使用CTE(通用表達式),可以通過創建視圖方式簡化SQL。
下面我們來看一個相對複雜的查詢,CTE可以簡化SQL並且減少嵌套,因爲可以預先定義輔助語句,之後在主查詢中多次調用。
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_utits,
sum(amount) as product_sales
from orders
where region in (select region from top_regions)
group by region, product;
定義了regional_sales和top_regions兩個輔助語句,regional_sales算出每個區域的總銷售額,top_regions算出銷售額在總銷售額10%以上的所有區域,主查詢通過輔助語句與orders表關聯,算出頂級區域每件商品的銷售額和銷售量。
2.遞歸查詢
with查詢的一個重要屬性是 RECURSIVE
,使用RECURSIVE
可以引用自己的輸出,從而實現遞歸,一般用於層次結構和樹狀結構中。
先舉一個簡單的例子
mydb@localhost:5432=>with recursive t(x) as (
mydb(> select 1
mydb(> union
mydb(> select x + 1
mydb(> from t
mydb(> where x < 5
mydb(> )
mydb-> select sum(x) from t;
sum
-----
15
(1 row)
再舉一個稍微複雜點的例子,
- 先創建一張表
mydb@localhost:5432=>create table test_area(id int4, name varchar(32), fatherid int4);
CREATE TABLE
- 向表裏面寫入測試數據
mydb@localhost:5432=>insert into test_area values
mydb-> (1, '中國', 0),
mydb-> (2, '遼寧', 1),
mydb-> (3, '山東', 1),
mydb-> (4, '瀋陽', 2),
mydb-> (5, '大連', 2),
mydb-> (6, '濟南', 3),
mydb-> (7, '和平區', 4),
mydb-> (8, '瀋河區', 4);
INSERT 0 8
現在查看下錶裏面的數據
mydb@localhost:5432=>select * from test_area;
id | name | fatherid
----+--------+----------
1 | 中國 | 0
2 | 遼寧 | 1
3 | 山東 | 1
4 | 瀋陽 | 2
5 | 大連 | 2
6 | 濟南 | 3
7 | 和平區 | 4
8 | 瀋河區 | 4
(8 rows)
上面的數據明顯具有層級關係
- 現在我們的需求是這個樣子的,給定一個id,可以完整的打印出來完整的地名,比如id=7,需要得到
中國遼寧瀋陽和平區
直接展示代碼
mydb@localhost:5432=>with recursive r as (
select * from test_area where id=7
union all
select test_area.* from test_area, r where test_area.id=r.fatherid
)
select * from r order by id;
id | name | fatherid
----+--------+----------
1 | 中國 | 0
2 | 遼寧 | 1
4 | 瀋陽 | 2
7 | 和平區 | 4
(4 rows)
接着我們使用string_agg
來合併起來
mydb@localhost:5432=>with recursive r as (
select * from test_area where id=7
union all
select test_area.* from test_area, r where test_area.id=r.fatherid
)
select string_agg(name, '') from (select name from r order by id) as n;
string_agg
--------------------
中國遼寧瀋陽和平區
(1 row)
- 入股我們修改下需求,例如給出id,我們需要得到id的地名和下面的所有子地名。以id=4爲例
mydb@localhost:5432=>with recursive r as (
mydb(> select * from test_area where id=4
mydb(> union all
mydb(> select test_area.* from test_area, r where test_area.fatherid=r.id
mydb(> )
mydb-> select * from r order by id;
id | name | fatherid
----+--------+----------
4 | 瀋陽 | 2
7 | 和平區 | 4
8 | 瀋河區 | 4
(3 rows)
使用CTE的好處
- 簡化SQL代碼的嵌套,提高代碼可讀性
- CTE的輔助語句只需要計算一次,在主查詢中可以反覆使用
- 當不需要共享查詢結果的時候,相比比視圖更輕量
PS:本文實例來自《PostgreSQL實戰》