根据运算符优先级解析SQL规则表达式

1.需求

测试数据库使用Greenplum,生产库使用GBase

普通表:存储客户数据,千万级别,结构如下

stat_date代表日期;user_id代表用户id;serial_number代表手机号;A_I72和A_I59是标签字段。

+---------------+--------------+------+-----+---------+-------+
| Field         | Type         | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+-------+
| stat_date     | varchar(255) | YES  |     | NULL    |       |
| user_id       | varchar(255) | YES  |     | NULL    |       |
| serial_number | varchar(255) | YES  |     | NULL    |       |
| A_I72         | varchar(255) | YES  |     | NULL    |       |
| A_I59         | varchar(255) | YES  |     | NULL    |       |
+---------------+--------------+------+-----+---------+-------+

字典表:记录表的字段的位置。

+-------------+---------+------+-----+---------+-------+
| Field       | Type    | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+-------+
| id          | int(11) | YES  |     | NULL    |       |
| column_name | text    | YES  |     | NULL    |       |
| table_name  | text    | YES  |     | NULL    |       |
+-------------+---------+------+-----+---------+-------+

根据优先级解析如下表达式,返回结果集(String类型手机号的List),解析式中出现的字段不一定在同一个表中,'&'表示求交集,'|'表示求并集。

(A_I72=1|A_I59=0)&serial_number=1369567xxxx

再将结果集放入Redis,给同事使用,千万级的数据预估5GB以上,要注意内存的清理(服务器内存配置64GB)

2.知识准备

我尝试过将形如'(A_I72=1|A_I59=0)&serial_number=1369567xxxx'的表达式放在数据库中查询,我能想到的就是用JOIN,因为字典表的存在,会出现子查询,效率低下;存储过程因为不方便调试和版本迭代,故弃用,最终采用Java来解析规则表达式

我发现这种规则表达式解析类似于运算符优先级的解析,模仿该博主代码,将其改造成业务代码

2.1 运算符优先级解析

先弄懂运算符优先级解析的原理,再继续逻辑

    /** 数字栈:用于存储表达式中的各个数字 */
    private Stack numberStack = null;
    /** 符号栈:用于存储运算符和括号 */
    private Stack symbolStack = null;

该博主用栈存储数字和符号,栈的特性众所周知,后进先出,为什么使用栈存储符号,稍后讲到

接下来看判断运算符优先级的方法

/**
     * 比较符号优先级:如果当前运算符比栈顶元素运算符优先级高则返回true,否则返回false
     * ✔
     */
    private boolean comparePri(char symbol) {
        if (symbolStack.empty()) { // 空栈返回true
            return true;
        }

        // 符号优先级说明(从高到低):
        // 第1级: (
        // 第2级: * /
        // 第3级: + -
        // 第4级: )

        char top = (char) symbolStack.peek(); // 查看堆栈顶部的对象,注意不是出栈
        if (top == '(') {
            return true;
        }
        // 比较优先级
        switch (symbol) { 
        case '(': // 优先级最高
            return true;
        case '*': {
            if (top == '+' || top == '-') // 优先级比+和-高
                return true;
            else
                return false;
        }
        case '/': {
            if (top == '+' || top == '-') // 优先级比+和-高
                return true;
            else
                return false;
        }
        case '+':
            return false;
        case '-':
            return false;
        case ')': // 优先级最低
            return false;
        case '=': // 结束符
            return false;
        default:
            break;
        }
        return true;
    }

comparePri方法比较运算符优先级,优先级降序排序如下:'(','* /','+ -',')',优先级在前的会先return true,结束switch,优先级低的元素会在栈底,优先级高的元素在栈顶参加运算,直到栈底上面的元素运算完毕,才轮到栈底元素

接下来,看判断表达式是否标准的方法

    private boolean isStandard(String numStr) {
        if (numStr == null || numStr.isEmpty()) // 表达式不能为空
            return false;
        //这里不使用栈也可以
        Stack stack = new Stack(); // 用来保存括号,检查左右括号是否匹配
        boolean b = false; // 用来标记'='符号是否存在多个
        for (int i = 0; i < numStr.length(); i++) {
            char n = numStr.charAt(i);
            // 判断字符是否合法
            //n+""将char转化成String
            if (!(isNumber(n) || "(".equals(n + "") || ")".equals(n + "")
                    || "+".equals(n + "") || "-".equals(n + "")
                    || "*".equals(n + "") || "/".equals(n + "")
                    || "=".equals(n + ""))) {
                return false;
            }
            // 将左括号压栈,用来给后面的右括号进行匹配
            if ("(".equals(n + "")) {
                stack.push(n);
            }
            if (")".equals(n + "")) { // 匹配括号
                //将左括号出栈,说明有右括号匹配到左括号
                if (stack.isEmpty() || !"(".equals((char) stack.pop() + "")) // 括号是否匹配
                    return false;
            }
            // 检查是否有多个'='号
            // 允许末尾出现一个'='号
            if ("=".equals(n + "")) {
                if (b)
                    return false;
                b = true;
            }
        }
        // 可能会有缺少右括号的情况
        if (!stack.isEmpty())
            return false;
        // 检查'='号是否不在末尾
        if (!("=".equals(numStr.charAt(numStr.length() - 1) + "")))
            return false;
        return true;
    }

isStandard方法匹配左括号,检测表达式末尾是否含有0个或1个'='

现在看核心的计算方法

/**
     * 解析并计算四则运算表达式(含括号),返回计算结果
     * ✔
     * @param numStr
     *            算术表达式(含括号)
     */
    public long caculate(String numStr) {
        numStr = removeStrSpace(numStr); // 去除空格
        // 如果算术表达式尾部没有‘=’号,则在尾部添加‘=’,表示结束符
        if (numStr.length() > 1 && !"=".equals(numStr.charAt(numStr.length() - 1) + "")) {
            numStr += "=";
        }
        // 检查表达式是否合法
        if (!isStandard(numStr)) {
            System.err.println("错误:算术表达式有误!");
            return 0;
        }
        // 初始化栈
        numberStack = new Stack();
        symbolStack = new Stack();
        // 用于缓存数字,因为数字可能是多位的
        StringBuffer temp = new StringBuffer();
        // 从表达式的第一个字符开始处理
        for (int i = 0; i < numStr.length(); i++) {
            char ch = numStr.charAt(i); // 获取一个字符
            if (isNumber(ch)) { // 若当前字符是数字
                temp.append(ch); // 加入到数字缓存中
            } else { // 非数字的情况
                String tempStr = temp.toString(); // 将数字缓存转为字符串
                if (!tempStr.isEmpty()) {
                    long num = Long.parseLong(tempStr); // 将数字字符串转为长整型数
                    numberStack.push(num); // 将数字压栈
                    temp = new StringBuffer(); // 重置数字缓存
                }
                // 判断运算符的优先级,若当前优先级低于栈顶的优先级,则先把计算前面计算出来
                while (!comparePri(ch) && !symbolStack.empty()) {
                    long b = numberStack.pop(); // 出栈,取出数字,后进先出
                    long a = numberStack.pop();
                    // 取出运算符进行相应运算,并把结果压栈进行下一次运算
                    switch ((char) symbolStack.pop()) {
                    case '+':
                        numberStack.push(a + b);
                        break;
                    case '-':
                        numberStack.push(a - b);
                        break;
                    case '*':
                        numberStack.push(a * b);
                        break;
                    case '/':
                        numberStack.push(a / b);
                        break;
                    default:
                        break;
                    }
                } // while循环结束
                if (ch != '=') {
                    // 优先级高的符号入栈
                    symbolStack.push(new Character(ch));
                    if (ch == ')') {
                        // 去掉左右括号
                        symbolStack.pop();
                        symbolStack.pop();
                    }
                }
            }
        } // for循环结束

        return numberStack.pop(); // 返回计算结果
    }

运算好的结果会放在numStack栈底,后进入的数字在栈顶等待运算。我推荐园友打断点调试此段代码,方便理解

完整代码戳这里

2.2 流程分析

我以‘3*5+2*10’为例分析运算符解析的流程

'*'运算符先入栈

根据运算符优先级解析SQL规则表达式_第1张图片

 

3入栈,5再入栈

根据运算符优先级解析SQL规则表达式_第2张图片

此时numStack中5出栈,3出栈,symbolStack中'*'出栈,进入swtich

完成乘法运算,将结果入栈

此时栈的情况如下

根据运算符优先级解析SQL规则表达式_第3张图片

现在symbolStack入栈'+',numStack入栈2,此时读取到了'*',根据comparePri方法,'*'在'+'前面,乘号具有更高的优先级,return true,此时入栈'*'。这里我们看到了栈在此处的妙用,低优先级的运算符在栈底等待运算,栈顶存放高优先级运算符;

然后numStack入栈10,栈的情况如下

根据运算符优先级解析SQL规则表达式_第4张图片

numStack中10和2出栈,symbolStack中'*'出栈,再进入switch,运算2*10,将结果20入栈numStack

此时栈的情况如下

根据运算符优先级解析SQL规则表达式_第5张图片

最后一步,numStack中20,15相继出栈,symbolStack中'+'出栈,将结果35入栈numStack,此时symbolStack为空,我们返回numStack.pop(),numStack出栈,此时numStack也为空

 

3.解决方案

我尝试过将形如'(A_I72=1|A_I59=0)&serial_number=1369567xxxx'的表达式放在数据库中查询,我能想到的就是用JOIN,因为字典表的存在,会出现子查询,效率低下;存储过程因为不方便调试和版本迭代,故弃用,最终采用Java来解析规则表达式

我发现这种规则表达式解析类似于运算符优先级的解析,模仿该博主代码将其改造成业务代码

3.1 思路1

优先级排序如下:'(' > '&' > '|' > ')'

优先级的解析我们已经做好,现在解决业务部分

symbolStack存储符号,numStack存储结果集。这里我们不能像刚才数字运算那么做,因为条件(id<=5)和结果集不能合并。我们创建一个LinkedHashMap>存储结果集,并引入UUID,根据UUID前缀来判断是否是结果集;前缀有"UUID"则是结果集,否则是普通条件,调用JDBC工具去数据库查询结果集。总共有四种情况:ab都含有'UUID_',ab都不含有"UUID_",a含有"UUID_,b不含",b含有"UUID_,a不含".

运算完毕,清除map,求交集使用retainAll方法,并集使用addAll方法

package com.advance.analysis;
import com.advance.JDBC.Connect_GBase;
import com.advance.Redis.RedisUtil;
import org.apache.log4j.Logger;
import org.testng.annotations.Test;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * @Author: 谷天乐
 * @Date: 2019/2/18 19:32
 * @Description: 修正版
 */
public class Analysis {
    public String UUID_PREFIX = "UUID_";

    public String UUID_PREFIX_AND = "UUID_And_";

    public String UUID_PREFIX_OR = "UUID_Or_";

    private static Logger logger = Logger.getLogger(Analysis.class);
    /**
     * 条件栈:用于存储表达式中的各个条件
     */
    private Stack numberStack = null;
    /**
     * 符号栈:用于存储运算符和括号
     */
    private Stack symbolStack = null;

    /**
     * 解析并计算规则表达式(含括号),返回计算结果
     *
     * @param numStr 规则表达式(含括号)
     */
    public LinkedHashMap> caculate(String numStr) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        numStr = removeStrSpace(numStr); // 去除空格
        // 如果规则表达式尾部没有‘#’号,则在尾部添加‘#’,表示结束符
        if (numStr.length() > 1 && !"#".equals(numStr.charAt(numStr.length() - 1) + "")) {
            numStr += "#";
        }
        // 检查表达式是否合法
        if (!isStandard(numStr)) {
            System.err.println("错误:规则表达式有误!");
            return null;
        }
        // 初始化栈
        numberStack = new Stack();
        symbolStack = new Stack();
        // 用于缓存条件,因为条件可能是多位的
        StringBuffer temp = new StringBuffer();
        //用于缓存执行sql的临时结果集
        //有序Map
        LinkedHashMap> tempMap = new LinkedHashMap<>();
        // 从表达式的第一个字符开始处理
        for (int i = 0; i < numStr.length(); i++) {
            char ch = numStr.charAt(i); // 获取一个字符
            if (!isSign(ch)) { // 若当前字符不是符号
                temp.append(ch); // 加入到条件缓存中
            } else { // 非数字的情况
                String tempStr = temp.toString(); // 将条件缓存转为字符串
                if (!tempStr.isEmpty()) {
                    numberStack.push(tempStr); // 将条件压栈
                    temp = new StringBuffer(); // 重置条件缓存
                }
                // 判断运算符的优先级,若当前优先级低于栈顶的优先级,则先把前面计算出来
                while (!comparePri(ch) && !symbolStack.empty()) {
                    String b = numberStack.pop(); // 出栈,取出条件,后进先出
                    String a = numberStack.pop();
                    // 取出运算符进行相应运算,并把结果压栈进行下一次运算
                    switch ((char) symbolStack.pop()) {
                        case '&':
                            if(a.contains(UUID_PREFIX)&&b.contains(UUID_PREFIX)){
                                bothHaveUUID(tempMap,"and");
                            }else if(!a.contains(UUID_PREFIX)&&b.contains(UUID_PREFIX)){
                                optionalUUID(tempMap,a,UUID_PREFIX_AND,"and");
                            }else if(a.contains(UUID_PREFIX)&&!b.contains(UUID_PREFIX)){
                                optionalUUID(tempMap,b,UUID_PREFIX_AND,"and");
                            } else {
                                bothDontHaveUUID(a,b,tempMap,UUID_PREFIX_AND,"and");
                            }
                            break;

                        case '|':
                            //根据列名查表名
                            if(a.contains(UUID_PREFIX)&&b.contains(UUID_PREFIX)){
                                bothHaveUUID(tempMap,"or");
                            }else if(!a.contains(UUID_PREFIX)&&b.contains(UUID_PREFIX)){
                                optionalUUID(tempMap,a,UUID_PREFIX_OR,"or");
                            }else if(a.contains(UUID_PREFIX)&&!b.contains(UUID_PREFIX)){
                                optionalUUID(tempMap,b,UUID_PREFIX_OR,"or");
                            }else {
                                bothDontHaveUUID(a,b,tempMap,UUID_PREFIX_OR,"or");
                            }
                            break;
                        default:
                            break;
                    }
                } // while循环结束
                if (ch != '#') {
                    symbolStack.push(new Character(ch)); // 符号入栈
                    if (ch == ')') { // 去括号
                        symbolStack.pop();
                        symbolStack.pop();
                    }
                }
            }
        } // for循环结束

        //使用entrySet会@throws ConcurrentModificationException
        //清缓存:去掉Map中无用的Set,计算过程中的Set在a含有'UUID',b不含有'UUID',
        //b含有'UUID',a不含有'UUID'的情况被清除
        //清除最终运算结果,即a和b都含有'UUID',a和b都不含有'UUID'的情况
        Set>> entries = tempMap.entrySet();
        Iterator>> iteratorMap = entries.iterator();
        while (iteratorMap.hasNext()){
            Map.Entry> next = iteratorMap.next();
            if(getTail(tempMap).getKey()!=next.getKey())
                iteratorMap.remove();
        }
        return tempMap; // 返回计算结果
    }

    /**
     * 去除字符串中的所有空格
     */
    private String removeStrSpace(String str) {
        return str != null ? str.replaceAll(" ", "") : "";
    }

    /**
     * 检查算术表达式的基本合法性,符合返回true,否则false
     */
    private boolean isStandard(String numStr) {
        if (numStr == null || numStr.isEmpty()) // 表达式不能为空
            return false;
        Stack stack = new Stack(); // 用来保存括号,检查左右括号是否匹配
        boolean b = false; // 用来标记'#'符号是否存在多个
        for (int i = 0; i < numStr.length(); i++) {
            char n = numStr.charAt(i);
            // 判断字符是否合法
            if ( !(isChar(n) || isNumber(n) || "(".equals(n + "") || ")".equals(n + "")
                    || ">".equals(n + "") || "<".equals(n + "")
                    || "&".equals(n + "") || "|".equals(n + "")
                    || "=".equals(n + "") || "#".equals(n + "")
                    || "_".equals(n + "") || "!".equals(n + "")
                    || "-".equals(n + "") || ".".equals(n + "")
                    || "'".equals(n + ""))) {
                return false;
            }
            // 将左括号压栈,用来给后面的右括号进行匹配
            if ("(".equals(n + "")) {
                stack.push(n);
            }
            if (")".equals(n + "")) { // 匹配括号
                if (stack.isEmpty() || !"(".equals((char) stack.pop() + "")) // 括号是否匹配
                    return false;
            }
            // 检查是否有多个'#'号
            if ("#".equals(n + "")) {
                if (b)
                    return false;
                b = true;
            }
        }
        // 可能会有缺少右括号的情况
        if (!stack.isEmpty())
            return false;
        // 检查'#'号是否不在末尾
        if (!("#".equals(numStr.charAt(numStr.length() - 1) + "")))
            return false;
        return true;
    }

    /**
     * 判断一个字符是否是 ( ) $ |等符号
     */
    private boolean isSign(char c) {
        if (c == '(' || c == ')' || c == '&' || c == '|' || c == '#') {
            return true;
        } else {
            return false;
        }
    }

    //提取列名
    private String extractColomnName(String str){
        //去掉比较运算符
        if(str.indexOf("<")!=-1)
            str = str.substring(0,str.indexOf("<"));
        if(str.indexOf("=")!=-1)
            str = str.substring(0,str.indexOf("="));
        if(str.indexOf(">")!=-1)
            str = str.substring(0,str.indexOf(">"));
        return str;
    }

    /**
     * 判断一个字符是否是 a-z  A-Z 之间的字母
     */
    private boolean isChar(char c) {
        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
            return true;
        } else {
            return false;
        }
    }


    /**
     * 判断字符是否是0-9的数字
     */
    private boolean isNumber(char num) {
        if (num >= '0' && num <= '9')
            return true;
        return false;
    }

    /**
     * 比较优先级:如果当前运算符比栈顶元素运算符优先级高则返回true,否则返回false
     */
    private boolean comparePri(char symbol) {
        if (symbolStack.empty()) { // 空栈返回ture
            return true;
        }

        // 符号优先级说明(从高到低):
        // 第1级: (
        // 第2级: $
        // 第3级: |
        // 第4级: )

        char top = (char) symbolStack.peek(); // 查看堆栈顶部的对象,注意不是出栈
        if (top == '(') {
            return true;
        }
        // 比较优先级
        switch (symbol) {
            case '(': // 优先级最高
                return true;
            case '&': {
                if (top == '|') // 优先级比|高
                    return true;
                else
                    return false;
            }
            case '|': {
                return false;
            }
            case ')': // 优先级最低
                return false;
            case '#': // 结束符
                return false;
            default:
                break;
        }
        return true;
    }

    //List转成String
    public String listToString(List list, char separator) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < list.size(); i++) {
            sb.append(list.get(i)).append(separator);
        }
        return sb.toString().substring(0, sb.toString().length() - 1);
    }

/*JDK1.8    public static  Map.Entry getTailByReflection(LinkedHashMap map)
            throws NoSuchFieldException, IllegalAccessException {
        Field tail = map.getClass().getDeclaredField("tail");
        tail.setAccessible(true);
        return (Map.Entry) tail.get(map);
    }*/

    public  Map.Entry getTail(LinkedHashMap map) {
        Iterator> iterator = map.entrySet().iterator();
        Map.Entry tail = null;
        while (iterator.hasNext()) {
            tail = iterator.next();
        }
        return tail;
    }

    /**
     * @Author 谷天乐
     * @Description a和b都含有'UUID_'
     * @Date 2019/2/25 11:25
     * @Param [tempMap, andor]
     * @return void
     **/
    public void bothHaveUUID(LinkedHashMap> tempMap,String andor){
        int num = 0;
        List keys = new ArrayList();
        for (Map.Entry> entry : tempMap.entrySet()) {
            keys.add(entry.getKey());
            num++;
        }
        Set resultAndSet1 = tempMap.get(keys.get(num-2));
        Set resultAndSet2 = tempMap.get(keys.get(num-1));
        //求交集
        if(andor.equals("and"))
            resultAndSet1.retainAll(resultAndSet2);
        if(andor.equals("or"))
            resultAndSet1.addAll(resultAndSet2);
        String andkey = String.valueOf(UUID.randomUUID());
        andkey = "UUID_And_" + andkey.toString().replace("-", "");
        numberStack.push(andkey);
        tempMap.put(andkey, resultAndSet1);
    }


    /**
     * @Author 谷天乐
     * @Description a和b都不含有"UUID_"
     * @Date 2019/2/25 12:37
     * @Param [conditionA, conditionB, tempMap, UUID_prefix, andor]
     * @return void
     **/
    public void bothDontHaveUUID(String conditionA,String conditionB,LinkedHashMap> tempMap,String UUID_prefix,String andor) throws SQLException, ClassNotFoundException {
        String aOrColumnName = extractColomnName(conditionA);
        String bOrColumnName = extractColomnName(conditionB);

        StringBuffer getOrTableSqlA = new StringBuffer("select table_name from table_dictionary ");
        getOrTableSqlA.append("where column_name=");
        getOrTableSqlA.append("\'");
        getOrTableSqlA.append(aOrColumnName);
        getOrTableSqlA.append("\'");
        ResultSet tableOrNameRSA = Connect_GBase.query(getOrTableSqlA.toString());
        List tableOrListA = Connect_GBase.resultSetToList(tableOrNameRSA, "table_name");
        String getOrTableSqlB = "select table_name from table_dictionary " +
                "where column_name=" + "\'" + bOrColumnName + "\'";
        ResultSet tableOrNameRSB = Connect_GBase.query(getOrTableSqlB);
        List tableOrListB = Connect_GBase.resultSetToList(tableOrNameRSB, "table_name");

        StringBuffer selectOrSql1 = new StringBuffer("select * from ");
        if(tableOrListA.size()==0)
            throw new RuntimeException("字段不存在");
        selectOrSql1.append(listToString(tableOrListA, ' '));
        selectOrSql1.append(" where ");
        selectOrSql1.append(conditionA);
        ResultSet rsOr1 = Connect_GBase.query(selectOrSql1.toString());

        StringBuffer selectOrSql2 = new StringBuffer("select * from ");
        if(tableOrListB.size()==0)
            throw new RuntimeException("字段不存在");
        selectOrSql2.append(listToString(tableOrListB, ' '));
        selectOrSql2.append(" where ");
        selectOrSql2.append(conditionB);
        ResultSet rsOr2 = Connect_GBase.query(selectOrSql2.toString());
        List resultOrList1 = Connect_GBase.resultSetToList(rsOr1, null);
        List resultOrList2 = Connect_GBase.resultSetToList(rsOr2, null);
        Set resultOrSet1 = new HashSet(resultOrList1);
        Set resultOrSet2 = new HashSet(resultOrList2);
        //求并集
        if(andor.equals("or"))
            resultOrSet1.addAll(resultOrSet2);
        if(andor.equals("and"))
            resultOrSet1.retainAll(resultOrSet2);
        //对每个条件进行执行,把执行出来的结果进行求并集操作,把结果放到栈中
        String orkey = String.valueOf(UUID.randomUUID());
        orkey = UUID_prefix + orkey.toString().replace("-", "");
        numberStack.push(orkey);
        tempMap.put(orkey, resultOrSet1);
    }

    /*
     * @Author 谷天乐
     * @Description Or 运算如下情况:
     * a含有'UU_ID',b不含有'UUID';b含有'UU_ID',a不含有'UUID'
     * @Date 2019/2/20 9:34
     * @Param [tempMap, condition]
     * @return void
     **/
    public void optionalUUID(LinkedHashMap> tempMap,String condition,String UUID_prefix,String andor) throws SQLException, ClassNotFoundException {
        int num = 0;
        List keys = new ArrayList();
        for (Map.Entry> entry : tempMap.entrySet()) {
            keys.add(entry.getKey());
            num++;
        }
        Set resultOrSet1 = tempMap.get(keys.get(num-1));
        //根据列名查表名
        String aOrColumnName = extractColomnName(condition);

        StringBuffer getOrTableSqlA = new StringBuffer("select table_name from table_dictionary ");
        getOrTableSqlA.append("where column_name=");
        getOrTableSqlA.append("\'");
        getOrTableSqlA.append(aOrColumnName);
        getOrTableSqlA.append("\'");
        ResultSet tableOrNameRSA = Connect_GBase.query(getOrTableSqlA.toString());
        List tableOrListA = Connect_GBase.resultSetToList(tableOrNameRSA, "table_name");
        if(tableOrListA.size()==0)
            throw new RuntimeException("字段不存在");

        StringBuffer selectOrSql2 = new StringBuffer("select * from ");
        selectOrSql2.append(listToString(tableOrListA, ' '));
        selectOrSql2.append(" where ");
        selectOrSql2.append(condition);
        ResultSet rsOr2 = Connect_GBase.query(selectOrSql2.toString());
        List resultOrList2 = Connect_GBase.resultSetToList(rsOr2, null);

        Set resultOrSet2 = new HashSet(resultOrList2);
        //求并集
        if (andor.equals("or"))
            resultOrSet1.addAll(resultOrSet2);
        if  (andor.equals("and"))
            resultOrSet1.retainAll(resultOrSet2);
        //移除使用过的Set
        tempMap.remove(keys.get(num-1));
        String orkey = String.valueOf(UUID.randomUUID());
        orkey = UUID_prefix + orkey.toString().replace("-", "");
        //对每个条件进行执行,把执行出来的结果进行求交集操作,把结果放到栈中
        numberStack.push(orkey);
        //--1、把a和b解析成sql并执行获取结果集,使用UUID生成key放入tempMap中,
        tempMap.put(orkey, resultOrSet1);
    }

    //根据Map获取指定key的value
    public Set getSpecifiedListFromMap(LinkedHashMap> resultMap, String key,String id) throws Exception{
        Set set = new HashSet<>();

        for (Map.Entry> entry : resultMap.entrySet()) {
            new RedisUtil().del(id);
            for(Map maplist : entry.getValue()){
                set.add(maplist.get(key));
                new RedisUtil().lpush(id,String.valueOf(maplist.get(key)));
            }
        }
        logger.debug(Analysis.class+"Jedis插入数据成功");
        return set;
    }

    @Test
    public void test() throws Exception {
        //a>=20 & (b<=40|c!=1) | d
        //id<=5&(id<=3|id<=5)|(amt=45.00|id<=6)
        //id<=5|id<=5&amt=45.00|id<=6
        //(date='2013-07-22'|date='2013-02-22')&id<=7&phone_number=18895358020
        String num = "(A_I72=1&A_I59=0)"; // 默认的算式
        Analysis analysis = new Analysis();
        LinkedHashMap> resultMap = analysis.caculate(num);
        getSpecifiedListFromMap(resultMap,"serial_number","ssss1111");
        logger.debug(resultMap);

    }
} 
  
 

3.2 思路2

先算结果集,再判断运算条件是否含有“UUID_”前缀,运算过程中清理map

这个是同事写的版本,代码少139行

package com.advance.analysis;

import com.advance.Engine_and_Message.util.NonUtil;
import com.advance.JDBC.Connect_Greenplum;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import java.sql.ResultSet;
import java.util.*;

/**
 * @Title:  Analysis_zk.java
 * @Description: 规则解析工具
 * @author: zhangkuo
 * @date:   2019-2-18 上午9:03:28
 */
public class Analysis_zk {
    private static Logger logger = LogManager.getLogger(Analysis.class);
    /**
     * 条件栈:用于存储表达式中的各个条件
     */
    private Stack numberStack = null;
    /**
     * 符号栈:用于存储运算符和括号
     */
    private Stack symbolStack = null;

    /**
     *
     * @Title: caculate
     * @Description: 解析并计算规则表达式(含括号),返回计算结果
     * @Author: zhangkuo
     * @param: @param numStr 规则表达式(含括号)
     * @param: @return
     * @param: @throws Exception
     * @return: LinkedHashMap>
     * @throws
     */
    public Set caculate(String numStr) throws Exception {
        numStr = removeStrSpace(numStr); // 去除空格
        // 如果规则表达式尾部没有‘#’号,则在尾部添加‘#’,表示结束符
        if (numStr.length() > 1 && !"#".equals(numStr.charAt(numStr.length() - 1) + "")) {
            numStr += "#";
        }
        // 检查表达式是否合法
        if (!isStandard(numStr)) {
            logger.debug("错误:规则表达式有误!");
            return null;
        }
        // 初始化栈
        numberStack = new Stack();
        symbolStack = new Stack();
        // 用于缓存条件,因为条件可能是多位的
        StringBuffer temp = new StringBuffer();
        //用于缓存执行sql的临时结果集
        //有序Map
        HashMap> tempMap = new HashMap<>();
        // 从表达式的第一个字符开始处理
        for (int i = 0; i < numStr.length(); i++) {
            char ch = numStr.charAt(i); // 获取一个字符
            if (!isSign(ch)) { // 若当前字符不是符号
                temp.append(ch); // 加入到条件缓存中
            } else { // 非数字的情况
                String tempStr = temp.toString(); // 将条件缓存转为字符串
                if (!tempStr.isEmpty()) {
                    numberStack.push(tempStr); // 将条件压栈
                    temp = new StringBuffer(); // 重置条件缓存
                }
                // 判断运算符的优先级,若当前优先级低于栈顶的优先级,则先把前面计算出来
                while (!comparePri(ch) && !symbolStack.empty()) {
                    String b = numberStack.pop(); // 出栈,取出条件,后进先出
                    String a = numberStack.pop();
                    // 取出运算符进行相应运算,并把结果压栈进行下一次运算
                    switch ((char) symbolStack.pop()) {
                        case '&':
                            Set set1 = this.calculator(b,tempMap);
                            Set set2 = this.calculator(a,tempMap);
                            String key = "UUID_"+UUID.randomUUID();
                            set1.retainAll(set2);
                            tempMap.put(key, set1);
                            numberStack.push(key);
                            if(a.contains("UUID_")){
                                tempMap.remove(a);
                            }
                            if(b.contains("UUID_")){
                                tempMap.remove(b);
                            }
                            break;
                        case '|':
                            Set set3 = this.calculator(b,tempMap);
                            Set set4 = this.calculator(a,tempMap);
                            String keyOr = "UUID_"+ UUID.randomUUID();
                            set3.addAll(set4);
                            tempMap.put(keyOr, set3);
                            numberStack.push(keyOr);
                            if(a.contains("UUID_")){
                                tempMap.remove(a);
                            }
                            if(b.contains("UUID_")){
                                tempMap.remove(b);
                            }
                            break;
                        default:
                            break;
                    }
                } // while循环结束
                if (ch != '#') {
                    symbolStack.push(new Character(ch)); // 符号入栈
                    if (ch == ')') { // 去括号
                        symbolStack.pop();
                        symbolStack.pop();
                    }
                }
            }
        } // for循环结束

        return tempMap.get(numberStack.pop().toString()); // 返回计算结果
    }



    //根据传入的字符串进行运算并返回结果集
    public Set calculator(String resultset,Map> tempMap) throws Exception{
        Set tempSet;
        if(resultset.contains("UUID_")){
            tempSet =  tempMap.get(resultset);
        }else{
            //根据列名查表名
            String columnName = extractColomnName(resultset);
            StringBuffer getTableName = new StringBuffer("select table_name from table_dictionary ");
            getTableName.append("where column_name=");
            getTableName.append("\'");
            getTableName.append(columnName);
            getTableName.append("\'");
            ResultSet tableName = Connect_Greenplum.query(getTableName.toString());
            List tableList = Connect_Greenplum.resultSetToList(tableName, "table_name");
            if(NonUtil.isNon(tableList)){
                throw new RuntimeException("字段不存在");
            }
            StringBuffer queryResult = new StringBuffer("select * from ");
            queryResult.append(listToString(tableList, ' '));
            queryResult.append(" where ");
            queryResult.append(resultset);
            ResultSet result = Connect_Greenplum.query(queryResult.toString());
            List resultList = Connect_Greenplum.resultSetToList(result, null);
            tempSet = new HashSet(resultList);
        }

        return tempSet;
    }



    /**
     * 去除字符串中的所有空格
     */
    private String removeStrSpace(String str) {
        return str != null ? str.replaceAll(" ", "") : "";
    }

    /**
     * 检查算术表达式的基本合法性,符合返回true,否则false
     */
    private boolean isStandard(String numStr) {
        if (numStr == null || numStr.isEmpty()) // 表达式不能为空
            return false;
        Stack stack = new Stack(); // 用来保存括号,检查左右括号是否匹配
        boolean b = false; // 用来标记'#'符号是否存在多个
        for (int i = 0; i < numStr.length(); i++) {
            char n = numStr.charAt(i);
            // 判断字符是否合法
            if ( !(isChar(n) || isNumber(n) || "(".equals(n + "") || ")".equals(n + "")
                    || ">".equals(n + "") || "<".equals(n + "")
                    || "&".equals(n + "") || "|".equals(n + "")
                    || "=".equals(n + "") || "#".equals(n + "")
                    || "_".equals(n + "") || "!".equals(n + "")
                    || "-".equals(n + "") || ".".equals(n + "")
                    || "'".equals(n + ""))) {
                return false;
            }
            // 将左括号压栈,用来给后面的右括号进行匹配
            if ("(".equals(n + "")) {
                stack.push(n);
            }
            if (")".equals(n + "")) { // 匹配括号
                if (stack.isEmpty() || !"(".equals((char) stack.pop() + "")) // 括号是否匹配
                    return false;
            }
            // 检查是否有多个'#'号
            if ("#".equals(n + "")) {
                if (b)
                    return false;
                b = true;
            }
        }
        // 可能会有缺少右括号的情况
        if (!stack.isEmpty())
            return false;
        // 检查'#'号是否不在末尾
        if (!("#".equals(numStr.charAt(numStr.length() - 1) + "")))
            return false;
        return true;
    }

    /**
     * 判断一个字符是否是 ( ) $ |等符号
     */
    private boolean isSign(char c) {
        if (c == '(' || c == ')' || c == '&' || c == '|' || c == '#') {
            return true;
        } else {
            return false;
        }
    }

    //提取列名
    private String extractColomnName(String str){
        //去掉比较运算符
        if(str.indexOf("<")!=-1)
            str = str.substring(0,str.indexOf("<"));
        if(str.indexOf("=")!=-1)
            str = str.substring(0,str.indexOf("="));
        if(str.indexOf(">")!=-1)
            str = str.substring(0,str.indexOf(">"));
        return str;
    }

    /**
     * 判断一个字符是否是 a-z  A-Z 之间的字母
     */
    private boolean isChar(char c) {
        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
            return true;
        } else {
            return false;
        }
    }


    /**
     * 判断字符是否是0-9的数字
     */
    private boolean isNumber(char num) {
        if (num >= '0' && num <= '9')
            return true;
        return false;
    }

    /**
     * 比较优先级:如果当前运算符比栈顶元素运算符优先级高则返回true,否则返回false
     */
    private boolean comparePri(char symbol) {
        if (symbolStack.empty()) { // 空栈返回ture
            return true;
        }

        // 符号优先级说明(从高到低):
        // 第1级: (
        // 第2级: $
        // 第3级: |
        // 第4级: )

        char top = symbolStack.peek(); // 查看堆栈顶部的对象,注意不是出栈
        if (top == '(') {
            return true;
        }
        // 比较优先级
        switch (symbol) {
            case '(': // 优先级最高
                return true;
            case '&': {
                if (top == '|') // 优先级比|高
                    return true;
                else
                    return false;
            }
            case '|': {
                return false;
            }
            case ')': // 优先级最低
                return false;
            case '#': // 结束符
                return false;
            default:
                break;
        }
        return true;
    }

    //List转成String
    public String listToString(List list, char separator) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < list.size(); i++) {
            sb.append(list.get(i)).append(separator);
        }
        return sb.toString().substring(0, sb.toString().length() - 1);
    }

    // 测试
    public static void main(String args[]) throws Exception {
        //a>=20 & (b<=40|c!=1) | d
        //id<=5&(id<=3|id<=5)|(amt=45.00|id<=6)
        //id<=5|id<=5&amt=45.00|id<=6
        String num = "(date='2013-02-22'&date='2013-03-01')|id<=3&amt>=500.00"; // 默认的算式
        Analysis_zk analysis = new Analysis_zk();
        Set resultMap = analysis.caculate(num);

        logger.debug(resultMap);
    }
}

 所需要的依赖包在这里

 

你可能感兴趣的:(根据运算符优先级解析SQL规则表达式)