一.WITH AS的含義
WITH AS短語,也叫做子查詢部分(subquery factoring),可以讓你做很多事情,定義一個SQL片斷,該SQL片斷會被整個SQL語句所用到。有的時候,是為了讓SQL語句的可讀性更高些,也有可能是在UNION ALL的不同部分,作為提供數據的部分。
特別對於UNION ALL比較有用。因為UNION ALL的每個部分可能相同,但是如果每個部分都去執行一遍的話,則成本太高,所以可以使用WITH AS短語,則只要執行一遍即可。如果WITH AS短語所定義的表名被調用兩次以上,則優化器會自動將WITH AS短語所獲取的數據放入一個TEMP表里,如果只是被調用一次,則不會。而提示materialize則是強制將WITH AS短語里的數據放入一個全局臨時表里。很多查詢通過這種方法都可以提高速度。
使用WITH AS 語句可以為一個子查詢語句塊定義一個名稱,使用這個子查詢名稱可以在查詢語句的很多地方引用這個子查詢。Oracle 數據庫像對待內聯視圖或臨時表一樣對待被引用的子查詢名稱,從而起到一定的優化作用。with子句是9i新增語法。
你可以在任何一個頂層的SELECT 語句以及幾乎所有類型的子查詢語句前,使用子查詢定義子句。被定義的子查詢名稱可以在主查詢語句以及所有的子查詢語句中引用,但未定義前不能引用。
with子句中不能嵌套定義<也就是with子句中不能有with子句>,但子查詢中出現的子查詢定義語句可以引用已定義的子查詢名稱。<可以引用前面已經定義的with子句>
二.使用方法
先看下面一個嵌套的查詢語句:
- select * from person.StateProvince where CountryRegionCode in
- (select CountryRegionCode from person.CountryRegion where Name like 'C%')
上面的查詢語句使用了一個子查詢。雖然這條SQL語句並不復雜,但如果嵌套的層次過多,會使SQL語句非常難以閱讀和維護。因此,也可以使用表變量的方式來解決這個問題,SQL語句如下:
- declare @t table(CountryRegionCode nvarchar(3))
- insert into @t(CountryRegionCode) (select CountryRegionCode from person.CountryRegion where Name like 'C%')
- select * from person.StateProvince where CountryRegionCode
- in (select * from @t)
雖然上面的SQL語句要比第一種方式更復雜,但卻將子查詢放在了表變量@t中,這樣做將使SQL語句更容易維護,但又會帶來另一個問題,就是性能的損失。由於表變量實際上使用了臨時表,從而增加了額外的I/O開銷,因此,表變量的方式並不太適合數據量大且頻繁查詢的情況。為此,在SQL Server 2005中提供了另外一種解決方案,這就是公用表表達式(CTE),使用CTE,可以使SQL語句的可維護性,同時,CTE要比表變量的效率高得多。
下面是CTE的語法:
- [ WITH <common_table_expression> [ ,n ] ]
- <common_table_expression>::=
- expression_name [ ( column_name [ ,n ] ) ]
- AS
- ( CTE_query_definition )
現在使用CTE來解決上面的問題,SQL語句如下:
- with cr as ( select CountryRegionCode from person.CountryRegion where Name like 'C%' ) select * from person.StateProvince
- nbsp;where CountryRegionCode in (select * from cr)
其中cr是一個公用表表達式,該表達式在使用上與表變量類似,只是SQL Server 2005在處理公用表表達式的方式上有所不同。
在使用CTE時應注意如下幾點:
1. CTE后面必須直接跟使用CTE的SQL語句(如select、insert、update等),否則,CTE將失效。如下面的SQL語句將無法正常使用CTE:
- with
- cr as
- (
- select CountryRegionCode from person.CountryRegion where Name like 'C%'
- )
- select * from person.CountryRegion -- 應將這條SQL語句去掉
- -- 使用CTE的SQL語句應緊跟在相關的CTE后面 --
- select * from person.StateProvince where CountryRegionCode in (select * from cr)
2. CTE后面也可以跟其他的CTE,但只能使用一個with,多個CTE中間用逗號(,)分隔,如下面的SQL語句所示:
- with
- cte1 as
- (
- select * from table1 where name like 'abc%'
- ),
- cte2 as
- (
- select * from table2 where id > 20
- ),
- cte3 as
- (
- select * from table3 where price < 100
- )
- select a.* from cte1 a, cte2 b, cte3 c where a.id = b.id and a.id = c.id
3. 如果CTE的表達式名稱與某個數據表或視圖重名,則緊跟在該CTE后面的SQL語句使用的仍然是CTE,當然,后面的SQL語句使用的就是數據表或視圖了,如下面的SQL語句所示:
- -- table1是一個實際存在的表
- with
- table1 as
- (
- select * from persons where age < 30
- )
- select * from table1 -- 使用了名為table1的公共表表達式
- select * from table1 -- 使用了名為table1的數據表
4. CTE 可以引用自身,也可以引用在同一 WITH 子句中預先定義的 CTE。不允許前向引用。
5. 不能在 CTE_query_definition 中使用以下子句:
(1)COMPUTE 或 COMPUTE BY
(2)ORDER BY(除非指定了 TOP 子句)
(3)INTO
(4)帶有查詢提示的 OPTION 子句
(5)FOR XML
(6)FOR BROWSE
6. 如果將 CTE 用在屬於批處理的一部分的語句中,那么在它之前的語句必須以分號結尾,如下面的SQL所示:
- declare @s nvarchar(3)
- set @s = 'C%'
- ; -- 必須加分號
- with
- t_tree as
- (
- select CountryRegionCode from person.CountryRegion where Name like @s
- )
- select * from person.StateProvince where CountryRegionCode in (select * from t_tree)
CTE除了可以簡化嵌套SQL語句外,還可以進行遞歸調用。
三.with as 子句相關總結
1.使用with子句可以讓子查詢重用相同的with查詢塊,通過select調用(with子句只能被select查詢塊引用),一般在with查詢用到多次情況下。在引用的select語句之前定義,同級只能定義with關鍵字只能使用一次,多個用逗號分割。
2.with子句的返回結果存到用戶的臨時表空間中,只做一次查詢,反復使用,提高效率。
3.在同級select前有多個查詢定義的時候,第1個用with,后面的不用with,並且用逗號隔開。
5.最后一個with 子句與下面的查詢之間不能有逗號,只通過右括號分割,with 子句的查詢必須用括號括起來
6.如果定義了with子句,而在查詢中不使用,那么會報ora-32035 錯誤:未引用在with子句中定義的查詢名。(至少一個with查詢的name未被引用,解決方法是移除未被引用的with查詢),注意:只要后面有引用 的就可以,不一定非要在主查詢中引用,比如后面的with查詢也引用了,也是可以的。
7.前面的with子句定義的查詢在后面的with子句中可以使用。但是一個with子句內部不能嵌套with子句。
8.當一個查詢塊名字和一個表名或其他的對象相同時,解析器從內向外搜索,優先使用子查詢塊名字。
9.with查詢的結果列有別名,引用的時候必須使用別名或*。
with子句優點:
1. SQL可讀性增強。比如對於特定with子查詢取個有意義的名字等。
2. with子查詢只執行一次,將結果存儲在用戶臨時表空間中,可以引用多次,增強性能。
with子句語法:
- With alias_name as (select1), --as和select中的括號都不能省略
- alias_name2 as (select2),--后面的沒有with,逗號分割,同一個主查詢同級別地方,with子查詢只能定義一次
- …
- alias_namen as (select n) –與下面的實際查詢之間沒有逗號
- Select ….
1.一般使用方式
如查詢銷售部門員工的姓名:
- --with clause
- with a as
- (select id from s_dept where name=Sales order by id)
- select last_name,title
- from s_emp where dept_id in (select * from a);--使用select查詢別名
使用with 子句,可以在復雜的查詢中預先定義好一個結果集,然后在查詢中反復使用,不使用會報錯。而且with 子句獲得的是一個臨時表,如果在查詢中使用,必須采用select from with 查詢名,比如
With cnt as(select count(*) from table)
Select cnt+1 from dual;
是錯誤的。必須是
With cnt as(select count(*) shumu from user_tables)
Select shumu+1 from cnt;
2.在大多數子查詢中引用,同級可見
再如下面兩個語句含義等價:
- with a as (select trade_id,name from product) --使用之前定義
- select name from a where a.trade_id in (
- with b as (select id from trademark where id=1) --使用之前定義
- select id from b
- );
- select name from product where trade_id in (select id from trademark where id=1);
3. with子查詢不可嵌套定義,但是后面的with定義可以引用前面的結果集。
- with select_trade as (select trade_id from product where id=1),
- --后面的with子查詢可以引用前面的結果
- select_trademark as (select name from trademark where id=(select trade_id from select_trade))
- select * from select_trademark;
- --這條語句錯誤
- with select_trade as
- --with中有嵌套with,不允許
- ( with temp as ( select trade_id from product where id=1)
- select trade_id from temp
- ),
- select_trademark as (select name from trademark where id=(select trade_id from select_trade))
- select * from select_trademark;
4. 同級定義,只能有一個with
- with a as (select trade_id from product),
- b as (select id from trademark where rownum<3) --第2個定義不用with
- select trade_id from a
- minus
- select id from b;
5. 在大多數子查詢中定義和使用,一個復雜的例子
- SELECT a ,b
- FROM (
- --第1個定義t_with
- WITH
- t_with AS (SELECT 1 a FROM DUAL)
- --子查詢使用t_with
- SELECT x.a ,(
- --內部定義了個t_with_z,並且使用t_with
- WITH t_with_z as (SELECT 1 a FROM t_with )
- SELECT s_1.a FROM t_with_z s_1 ,t_with s_2
6. 集合中使用with子查詢
- --這個是錯誤的,同級只能有一個with定義,當然兩個可以寫在一起。
- SELECT *
- FROM (
- WITH
- t_with_1 AS (SELECT 1 a ,1 b FROM DUAL)
- --此后的整個語句被認為是一個主查詢語句
- SELECT x.a , x.b FROM t_with_1 x
- union all
- WITH -- 此處再次出現定義
- t_with_2 AS (SELECT 1 a ,2 b FROM DUAL)
- SELECT y.a ,y.b FROM t_with_2 y
- --正確
- SELECT *
- FROM (
- --第1個with
- WITH
- t_with_1 AS (SELECT 1 a ,1 b FROM DUAL)
- SELECT x.a , x.b ,x c FROM t_with_1 x
- union all
- --這里不能用with再次定義,同級只能有1個with,可以寫在最前面
- --內部with
- SELECT y.a ,y.b , (WITH
- t_with_x AS (SELECT * FROM t_with_1)
- SELECT a FROM t_with_x )
- FROM ( WITH
- t_with_2 AS (SELECT 1 a ,2 b FROM DUAL)
- SELECT a ,b FROM t_with_2
- ) y
- WHERE y.a = (
- with t_with_3 AS (SELECT 1 a ,2 b FROM DUAL)
- select a from t_with_3
- )
- )
7.一個with查詢的實例:
查詢出部門的總薪水大於所有部門平均總薪水的部門。部門表s_dept,員工表s_emp。分析:做這個查詢,首先必須計算出所有部門的總薪 水,然后計算出總薪水的平均薪水,再篩選出部門的總薪水大於所有部門總薪水平均薪水的部門。那么第1 步with 查詢查出所有部門的總薪水,第2 步用with 從第1 步獲得的結果表中查詢出平均薪水,最后利用這兩次的with 查詢比較總薪水大於平均薪水的結果,如下:
- with
- --step1:查詢出部門名和部門的總薪水
- dept_costs as(
- select a.name,sum(b.salary) dept_total
- from
- s_dept a,s_emp b
- where a.id=b.dept_id
- group by a.name
- ),
- --step2:利用上一個with查詢的結果,計算部門的平均總薪水
- avg_costs as(
- select sum(dept_total)/count(*) dept_avg
- from dept_costs
- )
- --step3:從兩個with查詢中比較並且輸出查詢結果
- select name,dept_total
- from dept_costs
- where
- dept_total>
- (
- select dept_avg
- from
- avg_costs
- )
- order by name;
從上面的查詢可以看出,前面的with 查詢的結果可以被后面的with查詢重用,並且對with 查詢的結果列支持別名的使用,在最終查詢中必須要引用所有with 查詢,否則會報錯ora-32035 錯誤。
再如有這樣一個需求:一個查詢,如果查詢的結果行不滿足是10 的倍數,則補空行,直到是查詢出的行數是10 的倍數。例如:select * from trademark這個查詢。
11G R2with新特性:新版本的WITH 允許遞歸使用,在功能上比原來有了質的飛躍,可以實現許多原來CONNECT BY做不到的事。
with對執行計划的影響:with子句有可能會改變執行計划。
本文摘自:http://blog.163.com/weidaolan666@126/blog/static/49479943201171710305298/
http://wudataoge.blog.163.com/blog/static/80073886200961652022389/