SQL-在Update中进行子查询和左联查询

以下总结源自后边的三个参考思索和测试而来:

我们有一张行政区划表,为了查询速度的优化,我们需要在这张表中,将每个乡镇的记录中写入其所属的省、市、县,

表如下:

当然,我们可以使用游标或在存储过程中使用循环来实现这个这个需求,但是,那样做代码写起来似乎不是很简单方便,这里就不再过多讲述游标和存储过程的实现过程了,游标方式有想法的可以参考下边的参考外链(https://www.cnblogs.com/freshman0216/archive/2008/08/13/1267437.html),存储过程中的循环的核心代码如下:

declare @a int

set @a=61

while @a<=80

begin  

set @a+=1;

   --update t_area set temp_xian_name=null

end

这里我们主要讲述直接使用update子查询的方式来实现这个目标:

首页,我们要回归一个原始知识点的认知,因为这个知识点实在是太陌生了,那就是update是支持子查询的,而且,update语句的完整原始语法应该是:

update table set column=expression from table [where search_condition] 后面的一截可以说完全就是个查询语句。updatetable和查询的table(也就是from后面的table)完全一致的时候则可以省略,写成我们最常见的update table set column=expression [where search_condition]

但是,由于我们天天写后边这种形式且因为update语句中要求不得对update后边的表取别名,造成了我们潜意识地认为update后边只能在自已这一张表内wherewhere去地查询

于是,我们可以用一个清楚点的写法来呈现这个原始语法:

update t_area [BieMing] set [BieMing].temp_xian_name = [select] [BieMing].code [from] [BieMing] where  [BieMing].temp_xian_name is not null

其中,所有[]部分实际上都是可以(或者必须)被省略的

那么,假如我们不取别名,直接使用现有表名呢(或者说你就把现有表名当作别名),就变成这样:

update t_area set t_area.temp_xian_name = t_area.code from t_area where t_area.temp_xian_name is not null

而且,实践证明,这个语句是可以正常且正确执行的,为了与下边的内容看起来过渡地更自然一点,我们将上边这个语句整理一下格式,如下:

update t_area set t_area.temp_xian_name = 

t_area.code from t_area

where t_area.temp_xian_name is not null

这样看来,我们似乎就可以认为,在update的等号后边,我们还可以用[表名].[列名]的方式来访问我们正在操作的这张目标表,

那么,

回到我们的需求上来,我们似乎应该可以写出下边这样的语句:

update t_area set t_area.temp_xian_name = 

t_area.code from t_area

left join t_area y on t_area.parent_code=y.code

where t_area.temp_xian_name is not null

测试发现,OK,这是一个漂亮的过渡,

那么,

我们再稍变动一下:

update t_area set t_area.temp_xian_name = 

y.name from t_area

left join t_area y on t_area.parent_code=y.code

只要后边的查询语句返回的结果只有一条,这个语句就可以正常执行

于是,

我们就完成了我们的原始需求!

但是,我们上边使用的是左联的查询方式来完成了批量的子查询修改,那么,我们知道,子查询或者说查询,不止有左联这一种方式,真正的子查询用在这里能不能行呢,毕竟有些场景下左联的方式是不合适或不能用的,只能用真正的子查询,

于是,我们改一下语句:

update t_area set t_area.temp_xian_name = 

(select y.name from t_area y where y.code=t_area.parent_code)

或者

update t_area set t_area.temp_xian_name = 

(select top 1 y.name from t_area y where y.code=t_area.parent_code)

由于我们的需求中,我们要提取的数据还在t_area这同一张表中,让上边这个语句看起来略有些绕,你只看那个别名y,就把它当作另一张完全不相关的表就成了,我们将这个y表中的一个字段关联到t_area表的某个字段上,然后从y表中查询出唯一的一行数据并提取其中的列y.name的值交给外部语句使用,

测试证明,上述这两个真正的子查询也是正常且正确执行的。

关于这个update的子查询和以及引申出的左联查询的讲述就到此为止吧,希望能对各位有所帮助。(从Word中复制到博文中时,由于博客园这个编辑器的原因,有些文章样式丢了,一个个地添加太麻烦了,大家将就一下吧,也希望博客园能改进一下这个编辑器,毕竟某云盘的关停让我意识到硬盘才是王道,所以文章几乎都是先写到Word中然后才上传到博客园分享给大家的)

https://www.cnblogs.com/freshman0216/archive/2008/08/13/1267437.html

#参考:利用带关联子查询Update语句更新数据

      Update是T-sql中再简单不过的语句了,update table set column=expression  [where condition],我们都会用到。但update的用法不仅于此,真正在开发的时候,灵活恰当地使用update可以达到事半功倍的效果。

      假定有表Table1(a,b,c)和Table2(a,c),现在Table1中有些记录字段c为null,要根据字段a在Table2中查找,取出字段a相等的字段c的值来更新Table1。一种常规的思路,通过游标遍历Table1中字段c为null的所有记录,在循环体内查找Table2并进行更新,即用游标Cursor的形式。测试sql语句如下:


    --1.创建测试表
    create TABLE Table1
    (
        a varchar(10),
        b varchar(10),
        c varchar(10),
        CONSTRAINT [PK_Table1] PRIMARY KEY CLUSTERED
        (
            a ASC
        )
    ) ON [PRIMARY]

    create TABLE Table2
    (
        a varchar(10),
        c varchar(10),
        CONSTRAINT [PK_Table2] PRIMARY KEY CLUSTERED
        (
            a ASC
        )
    ) ON [PRIMARY]
    GO
    --2.创建测试数据
    Insert into Table1 values('','asds',null)
    Insert into Table1 values('','asds','100')
    Insert into Table1 values('','asds','80')
    Insert into Table1 values('','asds',null)

    Insert into Table2 values('','90')
    Insert into Table2 values('','100')
    Insert into Table2 values('','80')
    Insert into Table2 values('','95')
    GO
    select * from Table1

    --3.通过游标方式更新
    declare @name varchar(10)
    declare @score varchar(10)
    declare mycursor cursor for select a from Table1 where c is null
    open mycursor
    fetch next from mycursor into @name
    while(@@fetch_status = 0)
    BEGIN
        select @score=c from Table2 where a=@name
        update Table1 set c = @score where a = @name
        fetch next from mycursor into @name    
    END
    close mycursor
    deallocate mycursor
    GO
    --4.显示更新后的结果
    select * from Table1
    GO
    --5.删除测试表
    drop TABLE Table1
    drop TABLE Table2

 
     虽然用游标可以实现,但代码看起来很复杂,其实用Update根据子关联来更新只要一条语句就可以搞定了,测试代码如下:


    --1.创建测试表
    create TABLE Table1
    (
        a varchar(10),
        b varchar(10),
        c varchar(10),
        CONSTRAINT [PK_Table1] PRIMARY KEY CLUSTERED
        (
            a ASC
        )
    ) ON [PRIMARY]

    create TABLE Table2
    (
        a varchar(10),
        c varchar(10),
        CONSTRAINT [PK_Table2] PRIMARY KEY CLUSTERED
        (
            a ASC
        )
    ) ON [PRIMARY]
    GO
    --2.创建测试数据
    Insert into Table1 values('','asds',null)
    Insert into Table1 values('','asds','100')
    Insert into Table1 values('','asds','80')
    Insert into Table1 values('','asds',null)

    Insert into Table2 values('','90')
    Insert into Table2 values('','100')
    Insert into Table2 values('','80')
    Insert into Table2 values('','95')
    GO
    select * from Table1

    --3.通过Update方式更新
    Update Table1 set c = (select c from Table2 where a = Table1.a) where c is null
    GO

    --4.显示更新后的结果
    select * from Table1
    GO
    --5.删除测试表
    drop TABLE Table1
    drop TABLE Table2

 
      参考资料:也许是被忽略的update语句update 子查询

http://www.cnblogs.com/jimmyhsu/archive/2005/05/24/161387.html

#参考:也许是被忽略的update语句

   呵呵,园里的兄弟我又来写简单的文章了,希望大家不吝赐教。这次要说的是sql里面的update语句。说到这里,肯定会有很多兄弟不满意了,不就是update语句吗?谁不会写啊,update table set column=expression  [where search_condition 啊。

但真的update语句就仅仅这样吗?它的精髓它的原理是什么呢?我们通过例子来说明:
假设有一张成绩表,只有两个字段,姓名和成绩。怎样用一个sql语句查询出某个学生的姓名,成绩以及在表中的排名?
呵呵,这个问题是上次我发过的,详情见http://www.cnblogs.com/jimmyhsu/archive/2005/03/03/111884.aspx

结果可以去那里看。现在改改问题,假设这张成绩表有三个字段,姓名、成绩和排名,现在只有姓名和成绩有值,怎样将他们的排名更新上去呢?

有朋友可能会提出类似解决方案:先还是用查询语句查询出来,然后通过游标更新排名。
有没有更好的解决方案呢?当然是有的,答案就是用update语句。
 
update 成绩表 set 排名=(select count(*)+1 from 成绩表 where a.成绩<成绩)
from 成绩表 a

可以看到,只用一个语句就实现了。同时我们也可以发现它跟我们平常写的update语句不同,多了from其实update语句的原理和select语句很类似,完整的update语句应该是update table set column=expression from table [where search_condition] ,可见后面的一截完全就是个查询语句。当updatetable和查询的table(也就是from后面的table)完全一致的时候则可以省略,写成我们最常见的update table set column=expression [where search_condition]

上面仅仅是举了个例子来说明update的用法,真正在开发的时候,灵活恰当地使用update可以达到事半功倍的效果哦。

 

 

 

http://www.cnblogs.com/Fernando/archive/2007/12/17/1002978.html

#参考:update 子查询

基础知识

1 关联子查询和非关联子查询

在非关联子查询中,内部查询只执行一次并返回它的值给外部查询,然后外部查询在它的处理中使用内部查询返回给它的值。而在关联子查询中,对于外部查询返回的每一行数据,内部查询都要执行一次。另外,在关联子查询中是信息流是双向的。外部查询的每行数据传递一个值给子查询,然后子查询为每一行数据执行一次并返回它的记录。然后,外部查询根据返回的记录做出决策。

如:

SELECT o1.CustomerID, o1.OrderID, o1.OrderDate
FROM Orders o1
WHERE o1.OrderDate = (SELECT Max(OrderDate)
FROM Orders o2
WHERE o2.CustomerID = o1.CustomerID)

是一个关联子查询

SELECT o1.CustomerID, o1.OrderID, o1.OrderDate
FROM Orders o1
WHERE o1.OrderDate IN
(SELECT TOP 2 o2.OrderDate
FROM Orders o2
WHERE o2.CustomerID = o1.CustomerID)
ORDER BY CustomerID

是一个非关联子查询

2 提示(HINT

一般在优化时,无论采用基于规则的或是基于代价的方法,由Oracle系统的优化器来决定语句的执行路径。这样的选择的路径不要见得是最好的。所以,Oracle提供了一种方法叫提示的方法。它可以让编程人员按照自己的要求来选择执行路径,即提示优化器该按照什么样的执行规则来执行当前的语句。这样可以在性能上比起Oracle优化自主决定要好些。

通常情况下,编程人员可以利用提示来进行优化决策。通过运用提示可以对下面内容进行指定:

l SQL语句的优化方法;

对于某条SQL语句,基于开销优化程序的目标;

l SQL语句访问的访问路径;

连接语句的连接次序;

连接语句中的连接操作。

如果希望优化器按照编程人员的要求执行,则要在语句中给出提示。提示的有效范围有限制,即有提示的语句块才能按照提示要求执行。下面语句可以指定提示:

简单的SELECT ,UPDATE ,DELETE 语句;

复合的主语句或子查询语句;

组成查询(UNION)的一部分。

提示的指定有原来的注释语句在加“+”构成。语法如下:

[ SELECT | DELETE|UPDATE ] /*+ [hint | text ] */

[ SELECT | DELETE|UPDATE ] --+ [hint | text ]

注意在“/*”后不要空就直接加“+”,同样 “--+也是连着写。

警告:如果该提示语句书写不正确,则Oracle就忽略掉该语句。

常见的提示有:

Ordered 强制按照from子句中指定的表的顺序进行连接

Use_NL 强制指定两个表间的连接方式为嵌套循环(Nested Loops

Use_Hash 强制指定两个表间的连接方式为哈希连接(Hash Join

Use_Merge 强制指定两个表间的连接方式为合并排序连接(Merge Join

Push_Subq 让非关联子查询提前执行

Index 强制使用某个索引

3 执行计划

PL/SQL DeveloperSQL WINDOWS中用鼠标或键盘选中SQL语句,然后按F5,就会出现执行计划解析的界面:

4 Update的特点

Update的系统内部执行情况可以参照附文:对update事务的内部分析.doc

使用Update的基本要点就是,

1 尽量使用更新表上的索引,减少不必要的更新

2 更新的数据来源花费时间尽可能短,如果无法做到就把更新内容插入到中间表中,然后给中间表建上索引,再来更新

3 如果更新的是主键,建议删除再插入。

5 示例用表

后面的阐述将围绕以下两张表展开:

Create table tab1 (workdate varchar2(8), cino varchar2(15), val1 number, val2 number);
Create table tab2 (workdate varchar2(8), cino varchar2(15), val1 number, val2 number);
Create table tab3 (workdate varchar2(8), cino varchar2(15), val1 number, val2 number);
Create table tab4 (workdate varchar2(8), cino varchar2(15), val1 number, val2 number);workdate, cino为两张表的关键字,默认情况没有建主键索引。

二,Update两种情况

Update更新某个表,无外乎是两种情况:根据关联子查询,更新字段;通过非关联子查询,限定更新范围。如果还有第三种情况,那就是前两种情况的叠加。

1 根据关联子查询,更新字段

Update tab1 t
Set (val1, val2) = (select val1, val2
from tab2
where workdate = t.workdate
and cino = t.cino);

通过tab2来更新tab1的相应字段。执行SQL语句时,系统会从tab1中一行一行读记录,然后再通过关联子查询,找到相应的字段来更新。关联子查询能否通过tab1的条件快速的查找到对应记录,是优化能否实现的必要条件。所以一般都要求在tab2上建有Unique或者排重性较高的Normal索引。执行所用时间大概为(查询tab1中一条记录所用的时间 + tab2中查询一条记录所用的时间)* tab1中的记录条数。

如果子查询条件比较复杂,如以下语句:

Update tab1 t
Set (val1, val2) = (select val1, val2
from tab2 tt
where exists (select 1
from tab3
where workdate = tt.workdate
and cino = tt.cino)
and workdate = t.workdate
and cino = t.cino);这时更新tab1中的每条记录花费在子查询上的时间将成倍增加,如果tab1中的记录数较多,这种更新语句几乎是不可完成。

解决方式是,把子查询提取出来,做到中间表中,然后给中间表建上索引,用中间表来代替子查询,这样速度就能大大提高:

Insert into tab4
select workdate, cino, val1, val2
from tab2 tt
where exists (select 1
from tab3
where workdate = tt.workdate
and cino = tt.cino);
create index tab4_ind01 on tab4(workdate, cino);

Update tab1 t
Set (val1, val2) = (select val1, val2
from tab4 tt
where workdate = t.workdate
and cino = t.cino);

2 通过非关联子查询,限定更新范围

Update tab1 t
set val1 = 1
where (workdate, cino) in (select workdate, cino from tab2)

根据tab2提供的数据范围,来更新tab1中的相应记录的val1字段。

在这种情况下,系统默认执行方式往往是先执行select workdate, cino from tab2子查询,在系统中形成系统视图,然后在tab1中选取一条记录,查询系统视图中是否存在相应的workdate, cino组合,如果存在,则更新tab1,如果不存在,则选取下一条记录。这种方式的查询时间大致等于:子查询查询时间 + (在tab1中选取一条记录的时间 + 在系统视图中全表扫描寻找一条记录时间)* tab1的记录条数。其中“在系统视图中全表扫描寻找一条记录时间”会根据tab2的大小而有所不同。若tab2记录数较小,系统可以直接把表读到系统区中;若tab2记录数多,系统无法形成系统视图,这时会每一次更新动作,就把子查询做一次,速度会非常的慢。

针对这种情况的优化有两种

1 tab1上的workdate, cino字段上加入索引,同时增加提示。

修改以后的SQL语句如下:

Update /*+ordered use_nl(sys, t)*/ tab1 t
set val1 = 1
where (workdate, cino) in (select workdate, cino from tab2)

其中sys表示系统视图。如果不加入ordered提示,系统将会默认以tab1表作为驱动表,这时就要对tab1作全表扫描。加入提示后,使用系统视图,即select workdate, cino from tab2,作为驱动表,在正常情况下,速度能提高很多。

2 tab2表上的workdate, cino字段加入索引,同时改写SQL语句:

Update tab1 t
set val1 = 1
where exists (select 1
from tab2
where workdate = t.workdate
and cino = t.cino)

三,索引问题

update索引的使用比较特殊,有时看起来能用全索引,但实际上却只用到一部分,所以建议把复合索引的各字段写在一起。

例如:

Update /*+ordered use_nl(sys, t)*/ tab1 t
set val1 = 1
where cino in (select cino from tab2)
and workdate = '200506'

这条SQL语句是不能完全用到tab1上的复合索引workdate + cino的。能用到的只是workdate=’200506’的约束。

如果写成这样,就没问题:

Update /*+ordered use_nl(sys, t)*/ tab1 t
set val1 = 1
where (workdate, cino) in (select workdate, cino from tab2)

分类SQL

原文地址:https://www.cnblogs.com/liangjiang/p/9042078.html