根据运算符优先级解析SQL规则表达式
1.需求
测试数据库使用Greenplum,生产库使用GBase
普通表:存储客户数据,千万级别,结构如下
stat_date代表日期;user_id代表用户id;serial_number代表手机号;A_I72和A_I59是标签字段。
+---------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+-------+
| stat_date | varchar() | YES | | NULL | |
| user_id | varchar() | YES | | NULL | |
| serial_number | varchar() | YES | | NULL | |
| A_I72 | varchar() | YES | | NULL | |
| A_I59 | varchar() | YES | | NULL | |
+---------------+--------------+------+-----+---------+-------+
字典表:记录表的字段的位置。
+-------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+-------+
| id | int() | YES | | NULL | |
| column_name | text | YES | | NULL | |
| table_name | text | YES | | NULL | |
+-------------+---------+------+-----+---------+-------+
根据优先级解析如下表达式,返回结果集(String类型手机号的List),解析式中出现的字段不一定在同一个表中,'&'表示求交集,'|'表示求并集。
(A_I72=|A_I59=)&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工具去数据库查询结果集。总共有四种情况:a和b都含有'UUID_',a和b都不含有"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);
}
}
所需要的依赖包在这里
根据运算符优先级解析SQL规则表达式的更多相关文章
- Scala的运算符优先级:
运算符优先级决定术语的表达式分组.这会影响一个表达式是如何进行计算.某些运算符的优先级高于其他;例如,乘法运算符的优先级比所述加法运算符优先级更高: 例如X =7 + 3* 2;这里,x 被赋值13, ...
- Atitit.sql ast 表达式 语法树 语法 解析原理与实现 java php c#.net js python
Atitit.sql ast 表达式 语法树 语法 解析原理与实现 java php c#.net js python 1.1. Sql语法树 ast 如下图锁死1 2. SQL语句解析的思路和过程3 ...
- java表达式中运算符优先级
运算符优先级:运算符*和/(以及%)的优先级高于+和-(优先级越高,越早运算) 在逻辑运算符中,!拥有最高优先级,之后是&&,接下来是||. 一般来说,相同优先级的运算符的运算顺序是从 ...
- 运算符优先级 (JavaScript)
运算符优先级描述了在计算表达式时执行运算的顺序.先执行具有较高优先级的运算,然后执行较低优先级的运算.例如,先执行相乘,再执行相加. JavaScript 运算符 下表列出了 JavaScri ...
- 淘宝数据库OceanBase SQL编译器部分 源代码阅读--解析SQL语法树
OceanBase是阿里巴巴集团自主研发的可扩展的关系型数据库,实现了跨行跨表的事务,支持数千亿条记录.数百TB数据上的SQL操作. 在阿里巴巴集团下,OceanBase数据库支持了多个重要业务的数据 ...
- 《淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树》
淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树 曾经的学渣 2014-06-05 18:38:00 浏览1455 云数据库Oceanbase OceanBase是 ...
- 30分钟全面解析-SQL事务+隔离级别+阻塞+死锁
以前总是追求新东西,发现基础才是最重要的,今年主要的目标是精通SQL查询和SQL性能优化. 本系列主要是针对T-SQL的总结. [T-SQL基础]01.单表查询-几道sql查询题 [T-SQL基础] ...
- 深入解析SQL Server并行执行原理及实践(上)
在成熟领先的企业级数据库系统中,并行查询可以说是一大利器,在某些场景下他可以显著的提升查询的相应时间,提升用户体验.如SQL Server, Oracle等, Mysql目前还未实现,而Postgre ...
- (转)JavaScript中的运算符优先级
JavaScript中的运算符优先级是一套规则.该规则在计算表达式时控制运算符执行的顺序.具有较高优先级的运算符先于较低优先级的运算符执行.例如,乘法的执行先于加法. 下表按从最高到最低的优先级列出J ...
随机推荐
- Hadoop学习之路(二十三)MapReduce中的shuffle详解
概述 1.MapReduce 中,mapper 阶段处理的数据如何传递给 reducer 阶段,是 MapReduce 框架中 最关键的一个流程,这个流程就叫 Shuffle 2.Shuffle: 数 ...
- 摹客iDoc「标注」新玩法!这些细节让你爱不释手(201903-2版本更新)
哈喽小伙伴们,我们又见面啦!没错,小摹就是来告诉大家:摹客iDoc又双叒叕升级了!这次又上线了许多新玩法,在此之前,小摹先带大家温习一下iDoc以往的知识点: 攻城狮查看标注的利器 —— 标注信息智能 ...
- SPOJ - AMR11B
题目链接:https://www.spoj.com/problems/AMR11B/en/ 题目大意就是要你求图形覆盖的格点数,标记每个图形里的未标记格点(包括边界),总标记数就是覆盖的总格点数. # ...
- druid + spring 事务 + removeAbandonedTimeout 超时回收导致的问题
今天使用上述组合 做项目.. 在做一个需要较长时间使用数据库的 请求时,项目日志没有任何报错,但是数据库也没有插入代码. 初步猜测是 数据库连接超过 removeAbandonedTimeout 时间 ...
- 借助Algorithmia网站API:用AI给黑白照片上色,复现记忆中的旧时光
先看DEMOhttps://demos.algorithmia.com/colorize-photos/ 了解ColorfulImageColorizationhttps://algorithmia. ...
- idea执行mapreduce报错 Could not locate Hadoop executable: C:\hadoop-3.1.1\bin\winutils.exe
window执行mapreduce报错 Exception in thread "main" java.lang.RuntimeException: java.io.FileNot ...
- ABP框架系列之二十七:(Feature-Management-特征管理)
Introduction Most SaaS (multi-tenant) applications have editions (packages) those have different fea ...
- shell脚本学习-printf命令
跟着RUNOOB网站的教程学习的笔记 printf命令模仿C程序库里的printf()程序.printf由POSIX标准所定义,因此使用printf的脚本比使用echo有着更好的移植性. printf ...
- 从MATLAB到FPGA 视频和图像处理——讲座学习小结(视频地址https://ww2.mathworks.cn/videos/from-matlab-to-fpga-video-and-image-processing-102492.html)
1.HDLcoder产品介绍 图像处理分为两个部分: 这里主要讨论第一部分图像处理部分. 一般产品设计流程如下: 适用人群有以下两类: 这里先用一张slider来进行整体概括: 基于模型的设计的好处— ...
- Latex 自定义命令:用于一些特殊单词的显示
\usepackage{xspace} \newcommand{\ie}{{\emph{i.e.}},\xspace} \newcommand{\viz}{{\emph{viz.}},\xspace} ...