根據運算符優先級解析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<Long> numberStack = null;
    /** 符號棧:用於存儲運算符和括號 */
    private Stack<Character> 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<Character> stack = new Stack<Character>(); // 用來保存括號,檢查左右括號是否匹配
        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<Long>();
        symbolStack = new Stack<Character>();
        // 用於緩存數字,因為數字可能是多位的
        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’為例分析運算符解析的流程

'*'運算符先入棧

 

3入棧,5再入棧

此時numStack中5出棧,3出棧,symbolStack中'*'出棧,進入swtich

完成乘法運算,將結果入棧

此時棧的情況如下

現在symbolStack入棧'+',numStack入棧2,此時讀取到了'*',根據comparePri方法,'*'在'+'前面,乘號具有更高的優先級,return true,此時入棧'*'。這里我們看到了棧在此處的妙用,低優先級的運算符在棧底等待運算,棧頂存放高優先級運算符;

然后numStack入棧10,棧的情況如下

numStack中10和2出棧,symbolStack中'*'出棧,再進入switch,運算2*10,將結果20入棧numStack

此時棧的情況如下

最后一步,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<String, Set<Map>>存儲結果集,並引入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<String> numberStack = null;
    /**
     * 符號棧:用於存儲運算符和括號
     */
    private Stack<Character> symbolStack = null;

    /**
     * 解析並計算規則表達式(含括號),返回計算結果
     *
     * @param numStr 規則表達式(含括號)
     */
    public LinkedHashMap<String, Set<Map>> 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<String>();
        symbolStack = new Stack<Character>();
        // 用於緩存條件,因為條件可能是多位的
        StringBuffer temp = new StringBuffer();
        //用於緩存執行sql的臨時結果集
        //有序Map
        LinkedHashMap<String, Set<Map>> 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<Map.Entry<String, Set<Map>>> entries = tempMap.entrySet();
        Iterator<Map.Entry<String, Set<Map>>> iteratorMap = entries.iterator();
        while (iteratorMap.hasNext()){
            Map.Entry<String, Set<Map>> 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<Character> stack = new Stack<Character>(); // 用來保存括號,檢查左右括號是否匹配
        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 <K, V> Map.Entry<K, V> getTailByReflection(LinkedHashMap<K, V> map)
            throws NoSuchFieldException, IllegalAccessException {
        Field tail = map.getClass().getDeclaredField("tail");
        tail.setAccessible(true);
        return (Map.Entry<K, V>) tail.get(map);
    }*/

    public <K, V> Map.Entry<K, V> getTail(LinkedHashMap<K, V> map) {
        Iterator<Map.Entry<K, V>> iterator = map.entrySet().iterator();
        Map.Entry<K, V> 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<String, Set<Map>> tempMap,String andor){
        int num = 0;
        List keys = new ArrayList();
        for (Map.Entry<String, Set<Map>> 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<String, Set<Map>> 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<String, Set<Map>> tempMap,String condition,String UUID_prefix,String andor) throws SQLException, ClassNotFoundException {
        int num = 0;
        List keys = new ArrayList();
        for (Map.Entry<String, Set<Map>> 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<String, Set<Map>> resultMap, String key,String id) throws Exception{
        Set<Object> set = new HashSet<>();

        for (Map.Entry<String, Set<Map>> 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<String, Set<Map>> 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<String> numberStack = null;
    /**
     * 符號棧:用於存儲運算符和括號
     */
    private Stack<Character> symbolStack = null;

    /**
     *
     * @Title: caculate
     * @Description: 解析並計算規則表達式(含括號),返回計算結果
     * @Author: zhangkuo
     * @param: @param numStr 規則表達式(含括號)
     * @param: @return
     * @param: @throws Exception
     * @return: LinkedHashMap<String,Set<String>>
     * @throws
     */
    public Set<String> 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<String>();
        symbolStack = new Stack<Character>();
        // 用於緩存條件,因為條件可能是多位的
        StringBuffer temp = new StringBuffer();
        //用於緩存執行sql的臨時結果集
        //有序Map
        HashMap<String, Set<String>> 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<String> set1 = this.calculator(b,tempMap);
                            Set<String> 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<String> set3 = this.calculator(b,tempMap);
                            Set<String> 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<String> calculator(String resultset,Map<String,Set<String>> tempMap) throws Exception{
        Set<String> 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<Character> stack = new Stack<Character>(); // 用來保存括號,檢查左右括號是否匹配
        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<String> resultMap = analysis.caculate(num);

        logger.debug(resultMap);
    }
}

 所需要的依賴包在這里

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM