多个参数组合生成sql的方法

发布于:2025-08-04 ⋅ 阅读:(14) ⋅ 点赞:(0)

一、前言

最近开发,客户那边只提供了表结构,一部分测试数据,就让我们赶紧编码和测试;

没有测试数据,自测都难,只能看着代码自己造假数据。

更难的是,总有表是二三十列,然后最少也得七八个变量、每个变量又有四五种可能、还得排列组合、真的头大。

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直接自动生成,还是有用的。


网站公告

今日签到

点亮在社区的每一天
去签到