谈谈mysql update语句 set顺序问题、列交换sql实现及多表更新注意事项

  绝大多数数据库,在执行sql update语句时,update t set a = b, b=a 便可实现ab列值互换,赋值表达式右侧的值取的都是原始值。mysql则是例外,其单表更新是自左到右依次完成,即先完成a=b,然后在完成b=a(此时a=b),所以执行结果变成ab列都是b,然后多表更新则又不尊从该更新法则。本文讨论如何用sql实现类似编程语言引入tmp变量从而实现变量互换及mysql多表更新注意事项。

1、参考手册对于update的说明

mysql5.7参考手册,在第14章 14.2.11 UPDATE Syntax,是这样描述mysql 列更新顺序的,原文如下:
If you access a column from the table to be updated in an expression, UPDATE uses the current value of the column. For example, the following statement sets col1 to one more than its current value:

 UPDATE t1 SET col1 = col1 + 1

The second assignment in the following statement sets col2 to the current (updated) col1 value, not the original col1 value. The result is that col1 and col2 have the same value. This behavior differs from standard SQL.

UPDATE t1 SET col1 = col1 + 1, col2 = col1;

Single-table UPDATE assignments are generally evaluated from left to right. For multiple-table updates, there is no guarantee that assignments are carried out in any particular order.
说明:
1)单表更新赋值语句是,自左向右执行的,所以上例的col2 = col1(变化后的值,即col1 +1)
2)多表更新,赋值语句不确保任何给定的顺序执行,可能是原值,也可能是新值

2、mysql 两列互换sql实现

编程语言中,实现两个变量互换很简单:引入临时变量tmp,tmp = a,a = b,b = tmp即可实现ab互换,但是sql中没有临时变量,又如何实现变量互换呢?解决方案还是使用临时变量(只不过临时变量是某数据列的值,然后后面再覆盖该数据列的值),假设有a b列,a = 100,b = 1,实现a b互换,我们可以使用通用手法:
a = a + b, 101
b = a - b, 100
a = a - b, 1
至此,a = 1, b = 100,实现ab值互换,sql如下:

update t set a = a + b, b = a - b, a = a - b

3、mysql多表更新注意事项

两张表:pur_po_bill_detail(采购单细表),wm_sh_bill_detail(收货单细表),采购后,先根据采购单细表创建收货单,然后根据收货单入库。
pur_po_bill_detail(采购单细表)

CREATE TABLE `pur_po_bill_detail` (
  `sid` int(32) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `bill_id` bigint(64) DEFAULT NULL COMMENT 'po单号id',
  `bill_no` varchar(100) DEFAULT NULL COMMENT '订单号',
  `pw_count` decimal(20,4) DEFAULT NULL COMMENT '已入库数量',
  `th_count` decimal(20,4) DEFAULT NULL COMMENT '不合格数量',
  `bill_status` varchar(30) DEFAULT NULL COMMENT '状态',
  ...
  PRIMARY KEY (`sid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='采购订单细表'

wm_sh_bill_detail(收货单细表)

CREATE TABLE `wm_sh_bill_detail` (
  `sid` int(32) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `bill_id` bigint(64) DEFAULT NULL COMMENT '收货单id',
  `bill_no` varchar(30) DEFAULT NULL,
  `ref_number` varchar(30) DEFAULT NULL '采购单号',
  `ref_detail_sid` bigint(20) DEFAULT NULL '采购单行项目sid',
  `sh_count` decimal(20,4) DEFAULT NULL,
  `in_count` decimal(20,4) DEFAULT NULL,
  `left_count` decimal(20,4) DEFAULT NULL,
  ...
   PRIMARY KEY (`sid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='收货单细表'

下述sql是在收货单入库时反写采购单细表入库数量、状态。
当采购单行项目:入库数量 + 不合格退货数量 >= 订单数量,状态变成已入库

UPDATE pur_po_bill_detail t0, wm_sh_bill_detail t1
set t0.pw_count = coalesce(t0.pw_count,0) + t1.in_count, 
t0.th_count = coalesce(t0.th_count,0) + coalesce(t1.left_count,0),
t0.bill_status = case when t0.pw_count + t0.th_count >= t0.goods_count then '已入库' else t0.bill_status end
WHERE t0.sid = t1.ref_detail_sid and t1.bill_no = 'SH20180001';

sql执行结果失败,系mysql多表更新,在case判断时,t0.pw_count, t0.th_count取到的是原值。所以mysql多表更新需要注意:

  • 赋值语句、case语句尽量避免依赖引用,如本案case使用了赋值语句pw_count,th_count列,所以判断就出问题啦;
  • 可通过update语句拆分来实现多表复杂更新目标。

上述update语句拆分,先更新数量,然后更新状态:

UPDATE pur_po_bill_detail t0, wm_sh_bill_detail t1
set t0.pw_count = coalesce(t0.pw_count,0) + t1.in_count, 
t0.th_count = coalesce(t0.th_count,0) + coalesce(t1.left_count,0)
WHERE t0.sid = t1.ref_detail_sid and t1.bill_no = 'SH20180001';

UPDATE pur_po_bill_detail t0, wm_sh_bill_detail t1
set t0.bill_status = case when t0.pw_count + t0.th_count >= t0.goods_count then '已入库' else t0.bill_status end
WHERE t0.sid = t1.ref_detail_sid and t1.bill_no = 'SH20180001';

猜你喜欢

转载自blog.csdn.net/chuangxin/article/details/84558050