mysql SQL 中With as 的用法


原文链接: mysql SQL 中With as 的用法
  With alias_name  as (select1), -- as和select中的括号都不能省略
       alias_name2 as (select2), -- 后面的没有with,逗号分割,同一个主查询同级别地方,with子查询只能定义一次
       alias_namen as (select n) -- 与下面的实际查询之间没有逗号
  Select  * from  alias_name

一.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 [ ,n ] ]
::=

    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
    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)

  1. 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
    
    1. 如果CTE的表达式名称与某个数据表或视图重名,则紧跟在该CTE后面的SQL语句使用的仍然是CTE,当然,后面的SQL语句使用的就是数据表或视图了,如下面的SQL语句所示:

    -- table1是一个实际存在的表

    with
    table1 as
    (
    select * from persons where age < 30
    )
    select * from table1  -- 使用了名为 table1 的公共表表达式
    select * from table1  -- 使用了名为 table1 的数据表
    
  2. CTE 可以引用自身,也可以引用在同一 WITH 子句中预先定义的 CTE。不允许前向引用。

  3. 不能在 CTE_query_definition 中使用以下子句:

(1)COMPUTE 或 COMPUTE BY
(2)ORDER BY(除非指定了 TOP 子句)
(3)INTO
(4)带有查询提示的 OPTION 子句
(5)FOR XML
(6)FOR BROWSE

  1. 如果将 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 ….

  with使用例子:

  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;

  1. 在大多数子查询中定义和使用,一个复杂的例子

  

  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子句有可能会改变执行计划。

`