上期回顾
需求为:将如下TEST记录的ID=1的NAME改为ID=2的NAME的值,把ID=2的NAME改为ID=1的NAME的值。
drop table test; create table test (id number,name varchar2(20)); insert into test values (1,'a'); insert into test values (2,'b'); COMMIT; SQL> SELECT * FROM test; ID NAME ---------- -------------------- 1 a 2 b
如果执行如下:
UPDATE TEST SET NAME =(SELECT NAME FROM TEST WHERE ID=2) WHERE ID=1;
此时ID=1的NAME值已改变了,就不可能用如下来更新了
UPDATE TEST SET NAME =(SELECT NAME FROM TEST WHERE ID=1) WHERE ID=1;
如果是过程就很简单了,可以把原先的值先存储起来。但是是否单条SQL一定不行呢?
其实单条SQL是可以解决的,可考虑灵活利用MERGE特性!思路可考虑构造出一个虚拟表T,然后再根据此虚拟T表和真实的TEST表进行MERGE更新,就方便快捷的完成了。构造的虚拟表方法类似如下:
SQL> SELECT 1 id,(SELECT name FROM test WHERE id=2) name FROM DUAL 2 UNION ALL 3 SELECT 2,(SELECT name FROM test WHERE id=1) FROM DUAL 4 ; ID NAME ---------- -------------------- 1 b 2 a
有了此思路,结合前面所学的MERGE知识,可以通过如下简洁优雅定的代码完成更新。
SQL> MERGE INTO TEST 2 USING (SELECT 1 id,(SELECT name FROM test WHERE id=2) name FROM DUAL 3 UNION ALL 4 SELECT 2,(SELECT name FROM test WHERE id=1) FROM DUAL 5 ) t 6 ON (test.id = t.id) 7 WHEN MATCHED THEN UPDATE set TEST.name = t.name 8 ; Done SQL> SELECT * FROM test; ID NAME ---------- -------------------- 1 b 2 a
注:如果是9I固定需要INSERT,所以需要随便加上如下内容
WHEN NOT MATCHED THEN INSERT VALUES (1,'a')
本案例用的是MERGE的方法,当然,其中的构造虚拟表也是一个非常重要的思路,如果只是查询出改变后的结果而不是真实的进行更新,就可以不采用MERGE,直接可以采用如下方式取出结果
SQL> rollback; Rollback complete SQL> SQL> WITH T AS 2 (SELECT 1 id,(SELECT name FROM test WHERE id=2) name FROM DUAL 3 UNION ALL 4 SELECT 2,(SELECT name FROM test WHERE id=1) FROM DUAL 5 ) 6 SELECT test.id,t.name FROM test ,t 7 WHERE test.id=t.id; ID NAME ---------- -------------------- 1 b 2 a
通过MERGE可以得到一个非常有用的思想,就是如下:只要能查出更新后的结果集,就可利用该结果集来更新原表记录,即MERGE+ROWID方式。感谢NEWKID给予的指点,他精于使用此类方法,下文案例3中的复杂MERGE更新例子即来自NEWKID的精彩脚本。
本案例2来源于案例1的延伸,改变了案例1的处理思路,不再采用构造虚拟表T来关联TEST表的方式,而是直接把真实结果用SELECT的方式取出,然后利用这个结果集更新回原表中。
SQL> merge into test using 2 ( 3 WITH T AS 4 (SELECT 1 id,(SELECT name FROM test WHERE id=2) name FROM DUAL 5 UNION ALL 6 SELECT 2,(SELECT name FROM test WHERE id=1) FROM DUAL 7 ) 8 SELECT test.id,test.rowid as rn ,t.name FROM test ,t 9 WHERE test.id=t.id 10 ) n 11 on(test.rowid=n.rn) 12 when matched then update 13 set test.name=n.name; SQL> SELECT * FROM test; ID NAME ---------- -------------------- 1 a 2 b
注:直接UPDATE一个子查询的写法也可行,但是却有很多限制,稍微复杂的查询都易出错。此时用MERGE是最好的办法,结合ROWID的方式,可快速准确的利用一个已查询出的结果集来更新自己,是一个非常好的思路的扩展,希望对大家有借鉴。
在文章的最后,举一个USING里面有复杂的连接、聚合、分析函数的综合性例子来加深读者的印象,从而更深入的理解MERGE的强大功能!脚本选自NEWKID在答网友提问的一次精彩回复,提问需求如下:
declare
cursor c1 is
SELECT art_no,stock
FROM tb_fin_art_stock
WHERE run_date=to_date('&日期','yyyymmdd')-1 and art_no in (158756);
t_art tb_fin_art_stock.art_no%type;
t_stock tb_fin_art_STOCK.stock%type;
begin
open c1;
loop
fetch c1 into t_art,t_stock;
exit when c1%notfound;
update tb_fin_art_stock set stock=t_stock+gor_qty+return_qty-sale_qty+stock_corr+DEL_CORR
WHERE run_date=to_date('&日期','yyyymmdd')
and art_no=t_art;
commit;
end loop;
close c1;
end;
以上代码,我想更新tb_fin_art_stock 这个表中某个货号某一天往后所有记录的 stock(库存)字段的值,每天的stock是根据前一天的stock字段的值加进货减销售得出来的。现在只能一天一天更新。我想问的是如何能输入日期范围, 比如10号到20号的记录,根据9号更改10号,根据10号再改11号,。。。。以此类推,一次就更新了。
MERGE精彩解决方案如下,有兴趣的读者自行研究,相信必有收获!
MERGE INTO tb_fin_art_stock t USING (SELECT t.ROWID rid ,t2.stock+SUM(gor_qty+return_qty-sale_qty+stock_corr+DEL_CORR) OVER(PARTITION BY art_no ORDER BY run_date) AS stock FROM tb_fin_art_stock t ,(SELECT art_no,stock FROM tb_fin_art_stock WHERE run_date = lv_start_date-1) t2 WHERE t.run_date BETWEEN lv_start_date AND lv_end_date AND t.art_no = t2.art_no ) n USING (t.ROWID = n.rid) WHEN MATCHED THEN UPDATE SET t.stock = n.stock;

全文完
公众号:收获不止数据库
微信:ljbyxl1213
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/9091385/viewspace-3090571/ ,如需转载,请注明出处,否则将追究法律责任。