本文旨在讨论数据库应用中通过编程语言(Java、Python等)替代SQL的重要性,即SQL只负责简单的增、删、改、查操作,业务逻辑运算都由编程语言实现。我们通过一个在Code Review时碰到的真实场景谈谈编程语言和SQL各司其职的重要性。
1、修改前的代码片段
@SuppressWarnings("unchecked")
public Map<String, Double> getAvlMoney(Map<String, Object> para) {
DataAdapter adapter = new DataAdapter();
Map<String, Double> moneyMap = this.getClientMoney(para);
String sql = "select sum(money_with_tax) goods_money from sale_so_bill "
+ "where co_id = #co_id# and customer_id = #client_id# "
+ "and bill_status = '录入' and bill_type = '报损'";
Double baosun = TypeUtil.toDouble(adapter.getVar(sql, para), 0D);
Double avlfunds = 0D;
Double jiegai = 0D;
Double mianpin = 0D;
Double discount = 0D;
sql = "select sum(money_with_tax) goods_money, sum(goods_discount) goods_discount, "
+ "sum(goods_jiegai) goods_jg, sum(goods_mp) goods_mp "
+ "from sale_so_bill "
+ "where co_id = #co_id# and customer_id = #client_id# "
+ "and bill_status = '录入' and bill_type = '标准'";
Map<String, Object> m = adapter.queryForObject(sql, para);
if (m != null) {
avlfunds = TypeUtil.toDouble(m.get("goods_money"), 0D);
jiegai = TypeUtil.toDouble(m.get("goods_jg"), 0D);
mianpin = TypeUtil.toDouble(m.get("goods_mp"), 0D);
discount = TypeUtil.toDouble(m.get("goods_discount"), 0D);
}
sql = "select sum(goods_discount) from sale_so_bill "
+ "where co_id = #co_id# "
+ "and customer_id = #client_id# and bill_status = '录入' and bill_type = '广宣品-客户'";
discount += TypeUtil.toDouble(adapter.getVar(sql, para), 0D);
//刨除指定订单金额.
if (! TypeUtil.isEmpty(para.get("bill_id"))) {
sql = "select bill_type, money_with_tax goods_money, goods_jiegai, goods_mp, goods_discount "
+ "from sale_so_bill where co_id = #co_id# and sid = #bill_id#";
m = adapter.queryForObject(sql, para);
if (m != null) {
if ("报损".equals(m.get("bill_type"))) {
baosun = baosun - TypeUtil.toDouble(m.get("goods_money"), 0D);
} else {
avlfunds = avlfunds - TypeUtil.toDouble(m.get("goods_money"), 0D);
jiegai = jiegai - TypeUtil.toDouble(m.get("goods_jiegai"), 0D);
mianpin = mianpin - TypeUtil.toDouble(m.get("goods_mp"), 0D);
discount = discount - TypeUtil.toDouble(m.get("goods_discount"), 0D);
}
}
}
moneyMap.put("avlfunds", TypeUtil.doubleRound(moneyMap.get("avlfunds") - avlfunds, 2));
moneyMap.put("jiegai", TypeUtil.doubleRound(moneyMap.get("jiegai") - jiegai, 2));
moneyMap.put("mianpin", TypeUtil.doubleRound(moneyMap.get("mianpin") - mianpin, 2));
moneyMap.put("discount", TypeUtil.doubleRound(moneyMap.get("discount") - discount, 2));
moneyMap.put("baosun", TypeUtil.doubleRound(moneyMap.get("baosun") - baosun, 2));
return moneyMap;
}
代码说明:
1)不要考虑返回值类型的合理性(比如是否用BigDecimal保存金额等),DataAdapter是如何进行数据查询的,我们重点关注其运算逻辑;
2)该代码返回客户可报站资金(真实资金,折扣资金discount,免品资金mianpin,报损资金baosun等)
可报站资金 = 客户资金余额 - 已报站但是未发货的占用资金
客户资金余额, 通过Map<String, Double> moneyMap = this.getClientMoney(para)获取
moneyMap 扣减 报损订单报损资金,1次sql执行
moneyMap 扣减 标准订单资金、奖盖、折扣、免品,1次sql执行
moneyMap 扣减 光宣判订单折扣资金,1次sql执行
moneyMap 排除指定订单,1次sql执行
得到客户资金余额后,计算可报站资金,总共又耗费4次sql执行
3)通过分析,我们可以看出,得到客户资金余额后,计算可报站资金,只要1次sql执行得到大集合,然后java操作大集合即可,代码修改如下。
2、修改后的代码
@SuppressWarnings({ "unchecked", "rawtypes" })
public Map<String, Double> getAvlMoney(Map<String, Object> para) {
DataAdapter adapter = new DataAdapter();
Map<String, Double> moneyMap = this.getClientMoney(para);
String sql = "select goods_money, goods_funds, goods_zk, goods_jg, goods_mp from sale_so_bill "
+ "where co_id = #co_id# and client_id = #client_id# and status = '录入' "
+ "<dynamic>"
+ "<isNotEmpty property='bill_id' prepend='and'>sid != #bill_id#</isNotEmpty>"
+ "</dynamic>";
List<Map> list = adapter.queryForList(sql, para);
if (list == null) {
return moneyMap;
}
for (Map map : list) {
if ("报损".equals(map.get("bill_type"))) {
moneyMap.put("baosun", moneyMap.get("baosun") - TypeUtil.toDouble(map.get("baosun"), 0D));
} else if ("标准".equals(map.get("bill_type"))) {
moneyMap.put("avlfunds", moneyMap.get("avlfunds") - TypeUtil.toDouble(map.get("goods_funds"), 0D));
moneyMap.put("jiegai", moneyMap.get("jiegai") - TypeUtil.toDouble(map.get("goods_jg"), 0D));
moneyMap.put("discount", moneyMap.get("discount") - TypeUtil.toDouble(map.get("goods_zk"), 0D));
moneyMap.put("mianpin", moneyMap.get("mianpin") - TypeUtil.toDouble(map.get("goods_mp"), 0D));
} else if ("广宣品-客户".equals(map.get("bill_type"))) {
moneyMap.put("discount", moneyMap.get("discount") - TypeUtil.toDouble(map.get("goods_money"), 0D));
}
}
return moneyMap;
}
代码说明:
1)执行1次sql将符合要求的订单全部找出来;
2)根据moneyMap和订单资金进行运算,返回结果;
3、比较
1)修改后的代码,简洁,结果更清晰;
2)性能好很多
由于一个客户有效未发货的订单通常不会超过3单,因此一个sql返回的大集合和修改前分别返回的数据集合,数量级相当;
由于数据集合少,java循环运算成本几乎可忽略不计;
sql执行次数只有修改前的1/4,性能几乎可以提升3倍。
4、实际应用场景
尽可能多的编程语言实现,尽可能少的sql实现,有几个实际应用场景:
1)尽可能少用存储过程;
2)多个小查询看能不能替换成一个大查询+编程语言加工实现;