一、前言
最近开发,客户那边只提供了表结构,一部分测试数据,就让我们赶紧编码和测试;
没有测试数据,自测都难,只能看着代码自己造假数据。
更难的是,总有表是二三十列,然后最少也得七八个变量、每个变量又有四五种可能、还得排列组合、真的头大。
8个变量,每个变量5种可能,笛卡尔积就是5*5*5*5*5*5*5*5=390625
,一个表造假数据都得390625行,手写是不可能实现的。
实际使用时估计不会有这么多排列组合,有些业务上不用;但是自测的话又不清楚哪些用哪些不用;就算大部分都不用,零头的625行insert sql
,也不可能手写的出来。
二、解决思路
手写是不可能手写的,但是又得测试,客户又没给测试数据。
那只能考虑用代码实现了。
代码需要按照按照笛卡尔积格式替换变量,这种如果知道是几个变量的话,那就写死几个for循环也行;问题是写的通用一点的话,不知道总共几个变量,每个变量有几种变化也不知道。
准备用java代码实现,想了一下,大概需要以下格式:
入参:
sql模版(args[0]),变量组(args[1],args[2],args[3]…)
其中,假如有个5个变量,就输入5个变量组;
每个变量有几种形式,那就用逗号拼接,归类为一个变量组。
例如:
"insert into mytable(a,b,c) values( '${0}', '${1}', '${2}');","0,1,2","0,1","0,1,2"
其中,第一个参数是sql模版,${0}
这种是占位符,替换用;
"0,1,2","0,1","0,1,2"
表示有3组
变量,第一组变量有三种形式,可能是0/1/2
,以此类推。
这样,变量可能的形式有18种
排列组合:
000,001,002
010,011,012
100,101,102
110,111,112
200,201,202
210,211,212
如果是3^3变量数组,那么就是3*3*3=27
种组合;
不过不一定是满数组,现在的例子是3*2*3=18
种组合。
出参:
拼接好的sql语句
三、java代码
下面直接怼代码了。
需要2个类。
1.GitspringbootApplication.java
package com.my.gitspringboot;
import com.my.gitspringboot.util.SqlAnalyzeUtil;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GitspringbootApplication {
public static void main(String[] args) throws Exception {
//sql拼接
SqlAnalyzeUtil.analyzeSql(new String[]{"insert into mytable(a,b,c) values( '${0}', '${1}', '${2}');","0,1,2","0,1","0,1,2"});
//svn分析方法
//SVNAnalyzeUtil.doNormal(args);
}
}
这个类没什么说的,就是Main方法,当入口类用的;
入参见代码。
2.SqlAnalyzeUtil.java
package com.my.gitspringboot.util;
import java.util.ArrayList;
import java.util.Scanner;
public class SqlAnalyzeUtil {
//根据sql模版,${0}${1},根据这几个条件,使用${0}*${1}等等的方法,生成sql
//需要insert,以及delete
//需要个递归了
public static void analyzeSql(String[] args){
Scanner scanner = new Scanner(System.in);
//最终结果放这里面
StringBuilder sb = new StringBuilder();
String sql = args[0];
ArrayList<String[]> paramsList = new ArrayList<>();
//先整理好,第一个参数可能有String[]种,以此类推
for(int i=1;i<args.length;i++){
String[] split = args[i].split(",");
paramsList.add(split);
}
//总共有这么多列,这个当坐标用,刚开始都是0
int[] int_column = new int[paramsList.size()];
//刚开始,x用倒数第一个启动
finalSql(sql,paramsList.size()-1, 0, int_column, paramsList, sb);
System.out.println("按回车键继续...");
scanner.nextLine(); // 等待用户输入
//然后打印拼接好的sql
System.out.println("拼接好的sql如下:");
System.out.println(sb.toString());
System.out.println("按回车键退出...");
scanner.nextLine(); // 等待用户输入
}
public static void finalSql(String sql, int x, int y, int[] int_column, ArrayList<String[]> paramsList, StringBuilder sb){
//x和y是当前改变的坐标,先倒着算,好理解
//如果到了最后一个,那就退出
if(x <=0 && y>int_column[0]){
return;
}else{
StringBuilder log = new StringBuilder();
log.append("本次参数【");
//先按照 int_column 填写一个sql
String replace_sql = sql;
for(int i=0; i< int_column.length; i++){
log.append(int_column[i]).append(",");
replace_sql = replace_sql.replace("${"+i+"}", paramsList.get(i)[int_column[i]]);
}
log.append("】");
System.out.println(log.toString());
//这样就是一行sql写好了
sb.append(replace_sql).append("\r\n");
//y坐标++
y++;
//例如{3,3,3},总共3个参数,每个参数3种值;实际不一定,可能是{3,2,3}等等
//{3,3,3},坐标就从 000 开始,002的下一个,是010
//如果这个y遍历完了,那就是进位,
if(y>paramsList.get(x).length-1){
if(jinwei(int_column,x,y,paramsList)){
//进位成功,那就继续从最后一位开始跑
x = paramsList.size()-1;
y = 0;
}else{
//进位失败,就是跑完了
return;
}
}else{
int_column[x]++;
}
}
//继续下一轮
finalSql(sql, x, y, int_column, paramsList, sb);
}
public static boolean jinwei(int[] int_column, int x, int y, ArrayList<String[]> paramsList){
//如果进位失败,那就是跑完了,返回false
x--;
if(x < 0){
return false;
}else{
if(int_column[x]+1>paramsList.get(x).length-1){
//继续进位
return jinwei(int_column,x,y,paramsList);
}else{
int_column[x]++;
for(int i=int_column.length-1; i>x; i--){
int_column[i] = 0;
}
return true;
}
}
}
}
这个是核心代码,主要有以下几点:
1.jinwei
这个方法是进位方法,由于笛卡尔积类似满数组,所以就用递归的方式、写了一个进位方法;
每次只会从000->001->002->010
或者110->111->112->200
这样,从最右边开始进位,如果进位到左边一位,那么右边就全部从零开始。
2.finalSql
是拼接sql用的递归方法,跑一次后就会替换好一行sql、存入StringBuilder,然后开始跑下一行sql;
其中用了int_column数组、来表示当前变量是000
还是110
这样,替换sql时根据这个数组来替换就可以了,保证这个数组正常进位、就能实现笛卡尔积;
x与y是当前操作的数组下标,控制进位、停止递归等。
四、结果样例
经过测试,是可以实现多个参数的sql的。
如图:
打印了下参数坐标日志,确实与预期一致;
然后打印了需要的sql,生成好sql后、就可以批量放入数据库执行了。
五、备注
递归确实有点烧脑,听说不建议用递归;不过没想到怎么优化;
不用递归、代码肯定更多、说不定更难理解。
反正实现了功能就行了,自己用而已,几十几百条sql直接自动生成,还是有用的。