【Mybatis Plus】JSqlParser解析sql语句
JSqlParser 是一个用于解析 SQL 语句的 Java 库。它可以将 SQL 语句解析为一个 Java 对象树,允许你以编程的方式对 SQL 语句进行分析、修改和操作。它支持多种 SQL 语句类型,包括但不限于 SELECT、INSERT、UPDATE、DELETE、CREATE、ALTER 等。 例如,对于 SQL 语句 “SELECT column1, column2 FROM table1 WHERE column1 = ‘value’”,JSqlParser 可以将其解析为一个 Java 对象,你可以方便地访问该对象的各个部分,如 SELECT 子句中的列名(column1 和 column2)、表名(table1)以及 WHERE 子句中的条件(column1 = ‘value’)等。 使用 Maven 进行安装 (2)测试案例 首先,我们导入了 CCJSqlParserUtil 和 Statement 类,它们是 JSqlParser 的一部分。 你可以使用 JSqlParser 来解析 SQL 语句,以提取其中的关键信息。例如,如果你想知道一个 SELECT 语句选择了哪些列、查询了哪个表、使用了哪些条件等,可以通过 JSqlParser 进行解析。以下是一个简单的示例: (1)首先,我们使用 CCJSqlParserUtil.parse(sql) 将 SQL 语句解析为一个 Statement 对象。 你可以修改 SQL 语句的某些部分。例如,你可能想要将一个 SELECT 语句中的某些列替换为其他列,或者修改 WHERE 条件。以下是一个示例: (1)首先,我们按照上述的解析步骤将 SQL 语句解析为 PlainSelect 类型。 你可以使用 JSqlParser 来构建新的 SQL 语句。例如,你可以使用其 API 来创建一个 SELECT 语句,而不是手动编写 SQL 字符串。以下是一个简单的示例: (1)首先,我们创建表对象和列对象。 你可以使用 JSqlParser 来验证 SQL 语句的语法和结构。例如,在一个 SQL 编辑工具中,你可以使用 JSqlParser 来检查用户输入的 SQL 是否合法。以下是一个简单的示例: 我们使用 CCJSqlParserUtil.parse(sql) 尝试解析 SQL 语句,如果解析成功,说明 SQL 语句是合法的,否则会抛出 JSQLParserException,表明 SQL 语句存在问题。 以下是在使用 JSqlParser 时处理 SQL 注入攻击的一些方法: 在 Java 中,使用 JDBC 的预编译语句是防止 SQL 注入的重要手段,JSqlParser 可以与预编译语句结合使用。以下是一个简单的示例: (1)首先,我们使用 DriverManager.getConnection() 建立数据库连接。 JSqlParser 可以用来检查 SQL 语句是否符合预期,例如,可以检查 SQL 语句是否只包含允许的关键字和结构。以下是一个简单的示例: 我们使用 CCJSqlParserUtil.parse() 对 SQL 语句进行解析。 使用白名单来限制 SQL 语句中的表名、列名和操作。以下是一个简单的示例: 我们定义了一个允许的表名数组 ALLOWED_TABLES。 JSqlParser 可以帮助你将 SQL 语句转换为参数化查询对象,然后可以与预编译语句结合使用。以下是一个简单的示例: 我们使用 CCJSqlParserUtil.parse() 解析 SQL 语句。 (1)导入 JSqlParser 的相关类。 (1)首先,我们导入了 JSqlParser 所需的类,包括异常处理类 JSQLParserException,解析工具类 CCJSqlParserUtil,以及用于表示 SQL 语句的各种类,如 Statement、Select、SelectBody 和 SelectItem 等。 如果你要解析的 SQL 语句是 INSERT、UPDATE 或 DELETE 类型,你可以类似地将 Statement 对象转换为相应的类型,然后使用相应类型的方法提取所需的信息。例如: (1)对于 INSERT 语句,我们将 Statement 转换为 Insert 类型,然后可以使用 getTable() 方法获取插入的表名,getColumns() 方法获取插入的列名列表,getItemsList() 方法获取插入的值列表。 解析嵌套的 SQL 语句(如包含子查询、多层 JOIN 或 WITH 子句)通常需要借助 SQL 解析器工具,将 SQL 转换为结构化数据(如抽象语法树,AST),然后递归遍历其节点。以下是具体步骤和示例代码: 运行上述代码,输出如下: (1)子查询位置 (2)处理方法:通过递归遍历 SelectBody,逐层解析嵌套结构。 使用 ExpressionVisitorAdapter 访问 WHERE 条件中的子查询: 对于包含 UNION 的复杂查询,需处理 SetOperationList:【Mybatis Plus】JSqlParser解析sql语句
【一】JSqlParser 是什么
【二】JSqlParser 的安装步骤
(1)在 标签内添加以下依赖:<dependency><groupId>com.github.jsqlparser</groupId><artifactId>jsqlparser</artifactId><version>4.4</version></dependency>
importnet.sf.jsqlparser.parser.CCJSqlParserUtil;importnet.sf.jsqlparser.statement.Statement;publicclassJSqlParserExample{ publicstaticvoidmain(String[]args)throwsException{ Stringsql ="SELECT * FROM users WHERE id = 1";Statementstatement =CCJSqlParserUtil.parse(sql);System.out.println(statement);}}
在 main 方法中,我们定义了一个 SQL 语句字符串 sql。
然后,我们使用 CCJSqlParserUtil.parse(sql) 方法将 SQL 语句解析为一个 Statement 对象。
最后,我们将解析后的 Statement 对象打印出来。【三】使用场景
【1】sql语句解析
importnet.sf.jsqlparser.JSQLParserException;importnet.sf.jsqlparser.parser.CCJSqlParserUtil;importnet.sf.jsqlparser.statement.Statement;importnet.sf.jsqlparser.statement.select.Select;importnet.sf.jsqlparser.statement.select.SelectBody;importnet.sf.jsqlparser.statement.select.SelectItem;publicclassJSqlParserExample{ publicstaticvoidmain(String[]args){ Stringsql ="SELECT column1, column2 FROM table1 WHERE column1 = 'value'";try{ Statementstatement =CCJSqlParserUtil.parse(sql);if(statement instanceofSelect){ SelectselectStatement =(Select)statement;SelectBodyselectBody =selectStatement.getSelectBody();if(selectBody instanceofnet.sf.jsqlparser.statement.select.PlainSelect){ net.sf.jsqlparser.statement.select.PlainSelectplainSelect =(net.sf.jsqlparser.statement.select.PlainSelect)selectBody;List<SelectItem>selectItems =plainSelect.getSelectItems();for(SelectItemitem :selectItems){ System.out.println("Selected column: "+item);}System.out.println("Table: "+plainSelect.getTable());System.out.println("Where clause: "+plainSelect.getWhere());}}}catch(JSQLParserExceptione){ e.printStackTrace();}}}
(2)然后,我们将 Statement 对象转换为 Select 类型,因为我们知道这是一个 SELECT 语句。
(3)接着,我们通过 getSelectBody() 获取 SelectBody,并将其转换为 PlainSelect 类型,因为大多数简单的 SELECT 语句是 PlainSelect 类型。
(4)最后,我们可以使用 getSelectItems() 获取选择的列,getTable() 获取表名,getWhere() 获取 WHERE 子句。【2】SQL 语句转换
importnet.sf.jsqlparser.JSQLParserException;importnet.sf.jsqlparser.parser.CCJSqlParserUtil;importnet.sf.jsqlparser.statement.Statement;importnet.sf.jsqlparser.statement.select.Select;importnet.sf.jsqlparser.statement.select.SelectBody;importnet.sf.jsqlparser.statement.select.SelectItem;publicclassJSqlParserModifyExample{ publicstaticvoidmain(String[]args){ Stringsql ="SELECT column1, column2 FROM table1 WHERE column1 = 'value'";try{ Statementstatement =CCJSqlParserUtil.parse(sql);if(statement instanceofSelect){ SelectselectStatement =(Select)statement;SelectBodyselectBody =selectStatement.getSelectBody();if(selectBody instanceofnet.sf.jsqlparser.statement.select.PlainSelect){ net.sf.jsqlparser.statement.select.PlainSelectplainSelect =(net.sf.jsqlparser.statement.select.PlainSelect)selectBody;// 修改列名plainSelect.getSelectItems().clear();plainSelect.addSelectItems(CCJSqlParserUtil.parseSelectItem("column3, column4"));// 修改 WHERE 条件plainSelect.setWhere(CCJSqlParserUtil.parseCondExpression("column3 > 10"));}System.out.println("Modified SQL: "+statement);}}catch(JSQLParserExceptione){ e.printStackTrace();}}}
(2)然后,我们使用 getSelectItems().clear() 清除原有的选择项,并使用 addSelectItems() 添加新的选择项。
(3)最后,我们使用 setWhere() 修改 WHERE 条件。【3】SQL 语句生成
importnet.sf.jsqlparser.expression.Expression;importnet.sf.jsqlparser.expression.operators.conditional.AndExpression;importnet.sf.jsqlparser.expression.operators.relational.EqualsTo;importnet.sf.jsqlparser.expression.operators.relational.GreaterThan;importnet.sf.jsqlparser.parser.CCJSqlParserUtil;importnet.sf.jsqlparser.schema.Column;importnet.sf.jsqlparser.schema.Table;importnet.sf.jsqlparser.statement.select.PlainSelect;importnet.sf.jsqlparser.statement.select.Select;importnet.sf.jsqlparser.statement.select.SelectExpressionItem;publicclassJSqlParserCreateExample{ publicstaticvoidmain(String[]args){ // 创建表对象Tabletable =newTable("table1");// 创建列对象Columncolumn1 =newColumn("column1");Columncolumn2 =newColumn("column2");// 创建表达式 column1 = 'value'ExpressionequalsTo =newEqualsTo(column1,CCJSqlParserUtil.parseExpression("'value'"));// 创建表达式 column2 > 10ExpressiongreaterThan =newGreaterThan(column2,CCJSqlParserUtil.parseExpression("10"));// 创建 AND 表达式 column1 = 'value' AND column2 > 10Expressionwhere =newAndExpression(equalsTo,greaterThan);// 创建 SELECT 语句SelectExpressionItemselectItem1 =newSelectExpressionItem(column1);SelectExpressionItemselectItem2 =newSelectExpressionItem(column2);PlainSelectplainSelect =newPlainSelect();plainSelect.setSelectItems(List.of(selectItem1,selectItem2));plainSelect.setTable(table);plainSelect.setWhere(where);Selectselect =newSelect();select.setSelectBody(plainSelect);System.out.println("Generated SQL: "+select);}}
(2)然后,我们创建各种表达式,如 EqualsTo 表示等于条件,GreaterThan 表示大于条件,并使用 AndExpression 将它们组合成 WHERE 条件。
(3)接着,我们创建 SelectExpressionItem 作为选择项。
(4)最后,我们将这些元素组合成 PlainSelect 对象,再将其作为 Select 语句的 SelectBody。【4】SQL 语句验证
importnet.sf.jsqlparser.JSQLParserException;importnet.sf.jsqlparser.parser.CCJSqlParserUtil;publicclassJSqlParserValidationExample{ publicstaticvoidmain(String[]args){ Stringsql ="SELECT column1, column2 FROM table1 WHERE column1 = 'value'";try{ CCJSqlParserUtil.parse(sql);System.out.println("SQL is valid");}catch(JSQLParserExceptione){ System.out.println("SQL is invalid: "+e.getMessage());}}}
【四】在使用 JSqlParser 时,如何处理 SQL 注入攻击?
【1】使用预编译语句(Prepared Statements)
importjava.sql.Connection;importjava.sql.DriverManager;importjava.sql.PreparedStatement;importjava.sql.ResultSet;importjava.sql.SQLException;publicclassJSqlParserWithPreparedStatement{ publicstaticvoidmain(String[]args){ Stringurl ="jdbc:mysql://localhost:3306/your_database";Stringuser ="username";Stringpassword ="password";try(Connectionconnection =DriverManager.getConnection(url,user,password)){ // 假设解析后的 SQL 语句是一个 SELECT 语句StringparsedSql ="SELECT * FROM users WHERE username =?";try(PreparedStatementpreparedStatement =connection.prepareStatement(parsedSql)){ // 设置参数,这里假设用户输入来自于用户界面或其他来源StringuserInput ="admin";preparedStatement.setString(1,userInput);try(ResultSetresultSet =preparedStatement.executeQuery()){ while(resultSet.next()){ // 处理结果集System.out.println(resultSet.getString("username"));}}}}catch(SQLExceptione){ e.printStackTrace();}}}
(2)然后,我们定义一个包含占位符 ? 的 SQL 语句,这里的 ? 是预编译语句的占位符。
(3)使用 connection.prepareStatement() 创建预编译语句对象。
(4)通过 preparedStatement.setString() 等方法设置参数,这里的参数会被正确转义,避免了 SQL 注入的风险。【2】使用 JSqlParser 对 SQL 语句进行验证和规范化
importnet.sf.jsqlparser.JSQLParserException;importnet.sf.jsqlparser.parser.CCJSqlParserUtil;importnet.sf.jsqlparser.statement.Statement;publicclassJSqlParserValidation{ publicstaticvoidmain(String[]args){ Stringsql ="SELECT * FROM users WHERE username = 'admin' AND 1=1; DROP TABLE users;";try{ Statementstatement =CCJSqlParserUtil.parse(sql);// 这里可以添加更多的验证逻辑// 例如,检查是否包含不允许的关键字,如 DROP、TRUNCATE 等System.out.println("Parsed SQL: "+statement);}catch(JSQLParserExceptione){ e.printStackTrace();}}}
在解析后,可以添加额外的验证逻辑,例如检查 SQL 语句中是否包含 DROP、TRUNCATE 等危险的关键字,以防止恶意用户删除或修改数据库结构。【3】白名单机制
importnet.sf.jsqlparser.JSQLParserException;importnet.sf.jsqlparser.parser.CCJSqlParserUtil;importnet.sf.jsqlparser.statement.Statement;importnet.sf.jsqlparser.statement.select.Select;publicclassJSqlParserWhiteList{ publicstaticfinalString[]ALLOWED_TABLES={ "users","products"};publicstaticvoidmain(String[]args){ Stringsql ="SELECT * FROM users WHERE username = 'admin'";try{ Statementstatement =CCJSqlParserUtil.parse(sql);if(statement instanceofSelect){ Selectselect =(Select)statement;// 假设我们只允许查询 users 或 products 表StringtableName =select.getSelectBody().toString().split("FROM")[1].trim().split(" ")[0];if(!isAllowedTable(tableName)){ thrownewRuntimeException("Table not allowed");}System.out.println("Parsed SQL: "+statement);}}catch(JSQLParserExceptione){ e.printStackTrace();}}privatestaticbooleanisAllowedTable(StringtableName){ for(StringallowedTable :ALLOWED_TABLES){ if(allowedTable.equalsIgnoreCase(tableName)){ returntrue;}}returnfalse;}}
解析 SQL 语句后,对于 SELECT 语句,我们提取出表名,并检查它是否在白名单中。【4】使用参数化查询对象
importnet.sf.jsqlparser.JSQLParserException;importnet.sf.jsqlparser.expression.Expression;importnet.sf.jsqlparser.parser.CCJSqlParserUtil;importnet.sf.jsqlparser.statement.Statement;importnet.sf.jsqlparser.statement.select.Select;publicclassJSqlParserParameterized{ publicstaticvoidmain(String[]args){ Stringsql ="SELECT * FROM users WHERE username = 'admin' AND age > 20";try{ Statementstatement =CCJSqlParserUtil.parse(sql);if(statement instanceofSelect){ Selectselect =(Select)statement;// 假设这里可以提取表达式,如 username = 'admin' 和 age > 20ExpressionwhereExpression =((Select)statement).getSelectBody().toString().split("WHERE")[1].trim();// 这里可以进一步处理表达式,将其转换为参数化查询对象System.out.println("Parsed Expression: "+whereExpression);}}catch(JSQLParserExceptione){ e.printStackTrace();}}}
对于 SELECT 语句,我们可以提取 WHERE 子句的表达式,将其作为参数化查询对象,然后与预编译语句结合使用,进一步避免 SQL 注入风险。【五】使用 JSqlParser 解析复杂的 SQL 语句
【1】思路
(2)创建一个 SQL 语句的字符串。
(3)使用 CCJSqlParserUtil.parse() 方法将 SQL 语句解析为 Statement 对象。
(4)根据 SQL 语句的不同类型(例如 Select、Insert、Update、Delete),将 Statement 对象进行类型转换。
(5)对转换后的对象进行进一步的操作,提取所需的信息。【2】示例代码
importnet.sf.jsqlparser.JSQLParserException;importnet.sf.jsqlparser.parser.CCJSqlParserUtil;importnet.sf.jsqlparser.statement.Statement;importnet.sf.jsqlparser.statement.select.Select;importnet.sf.jsqlparser.statement.select.SelectBody;importnet.sf.jsqlparser.statement.select.SelectItem;importjava.util.List;publicclassJSqlParserComplexExample{ publicstaticvoidmain(String[]args){ StringcomplexSql ="SELECT column1, column2, SUM(column3) AS total FROM table1 WHERE column1 > 10 GROUP BY column1, column2 HAVING SUM(column3) > 100 ORDER BY column1 ASC, column2 DESC";try{ // 将 SQL 语句解析为 Statement 对象Statementstatement =CCJSqlParserUtil.parse(complexSql);// 判断 Statement 对象是否为 Select 语句if(statement instanceofSelect){ SelectselectStatement =(Select)statement;SelectBodyselectBody =selectStatement.getSelectBody();// 提取 Select 语句中的 SelectItemsif(selectBody instanceofnet.sf.jsqlparser.statement.select.PlainSelect){ net.sf.jsqlparser.statement.select.PlainSelectplainSelect =(net.sf.jsqlparser.statement.select.PlainSelect)selectBody;List<SelectItem>selectItems =plainSelect.getSelectItems();for(SelectItemitem :selectItems){ System.out.println("Select Item: "+item);}// 提取 Where 条件if(plainSelect.getWhere()!=null){ System.out.println("Where Clause: "+plainSelect.getWhere());}// 提取 Group By 子句if(plainSelect.getGroupBy()!=null){ System.out.println("Group By Clause: "+plainSelect.getGroupBy());}// 提取 Having 子句if(plainSelect.getHaving()!=null){ System.out.println("Having Clause: "+plainSelect.getHaving());}// 提取 Order By 子句if(plainSelect.getOrderByElements()!=null){ System.out.println("Order By Clause: "+plainSelect.getOrderByElements());}}}}catch(JSQLParserExceptione){ e.printStackTrace();}}}
(2)在 main 方法中,我们定义了一个复杂的 SQL 语句字符串 complexSql。
(3)然后,我们使用 CCJSqlParserUtil.parse(complexSql) 方法将这个复杂的 SQL 语句解析为一个 Statement 对象。
(4)接下来,我们检查这个 Statement 对象是否是 Select 语句(因为我们的示例是一个 SELECT 语句),如果是,我们将其转换为 Select 类型。
(5)对于 Select 语句,我们进一步提取 SelectBody,并判断它是否是 PlainSelect 类型,因为大多数简单的 SELECT 语句会使用 PlainSelect 结构。
(6)我们可以使用 getSelectItems() 方法获取 SELECT 子句中的所有选择项,并遍历打印它们。
(7)对于 WHERE 子句,我们可以使用 getWhere() 方法获取条件表达式,如果存在的话。
(8)对于 GROUP BY 子句,我们可以使用 getGroupBy() 方法获取分组信息,如果存在的话。
(9)对于 HAVING 子句,我们可以使用 getHaving() 方法获取过滤条件,如果存在的话。
(10)对于 ORDER BY 子句,我们可以使用 getOrderByElements() 方法获取排序信息,如果存在的话。importnet.sf.jsqlparser.JSQLParserException;importnet.sf.jsqlparser.parser.CCJSqlParserUtil;importnet.sf.jsqlparser.statement.Statement;importnet.sf.jsqlparser.statement.delete.Delete;importnet.sf.jsqlparser.statement.insert.Insert;importnet.sf.jsqlparser.statement.update.Update;publicclassJSqlParserOtherExamples{ publicstaticvoidmain(String[]args){ StringinsertSql ="INSERT INTO table1 (column1, column2) VALUES (1, 'value')";StringupdateSql ="UPDATE table1 SET column1 = 2 WHERE column2 = 'value'";StringdeleteSql ="DELETE FROM table1 WHERE column1 = 3";try{ // 解析 INSERT 语句StatementinsertStatement =CCJSqlParserUtil.parse(insertSql);if(insertStatement instanceofInsert){ Insertinsert =(Insert)insertStatement;System.out.println("Insert Table: "+insert.getTable());System.out.println("Insert Columns: "+insert.getColumns());System.out.println("Insert Values: "+insert.getItemsList());}// 解析 UPDATE 语句StatementupdateStatement =CCJSqlParserUtil.parse(updateSql);if(updateStatement instanceofUpdate){ Updateupdate =(Update)updateStatement;System.out.println("Update Table: "+update.getTable());System.out.println("Update Set Items: "+update.getSets());System.out.println("Update Where Clause: "+update.getWhere());}// 解析 DELETE 语句StatementdeleteStatement =CCJSqlParserUtil.parse(deleteSql);if(deleteStatement instanceofDelete){ Deletedelete =(Delete)deleteStatement;System.out.println("Delete Table: "+delete.getTable());System.out.println("Delete Where Clause: "+delete.getWhere());}}catch(JSQLParserExceptione){ e.printStackTrace();}}}
(2)对于 UPDATE 语句,我们将 Statement 转换为 Update 类型,然后可以使用 getTable() 方法获取更新的表名,getSets() 方法获取更新的列和值的映射,getWhere() 方法获取更新的条件。
(3)对于 DELETE 语句,我们将 Statement 转换为 Delete 类型,然后可以使用 getTable() 方法获取删除的表名,getWhere() 方法获取删除的条件。【六】解析嵌套sql的案例
【1】解析 SQL 并遍历嵌套结构
importnet.sf.jsqlparser.JSQLParserException;importnet.sf.jsqlparser.parser.CCJSqlParserUtil;importnet.sf.jsqlparser.statement.Statement;importnet.sf.jsqlparser.statement.select.*;publicclassNestedSqlParser{ publicstaticvoidmain(String[]args)throwsJSQLParserException{ Stringsql ="SELECT u.name, (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS order_count "+"FROM users u "+"WHERE u.id IN (SELECT user_id FROM active_users WHERE status = 'ACTIVE')";Statementstatement =CCJSqlParserUtil.parse(sql);if(statement instanceofSelect){ Selectselect =(Select)statement;SelectBodyselectBody =select.getSelectBody();processSelectBody(selectBody,0);// 从嵌套层级 0 开始}}/** * 递归处理 SELECT 语句的嵌套结构 * @param selectBody 当前层级的 SELECT 主体 * @param level 嵌套层级(用于缩进输出) */privatestaticvoidprocessSelectBody(SelectBodyselectBody,intlevel){ if(selectBody instanceofPlainSelect){ PlainSelectplainSelect =(PlainSelect)selectBody;// 输出当前层级的 SELECTSystem.out.println(indent(level)+"SELECT层级: "+level);// 处理子查询(嵌套 SELECT)for(SelectItemitem :plainSelect.getSelectItems()){ if(item instanceofSelectExpressionItem){ SelectExpressionItemexprItem =(SelectExpressionItem)item;if(exprItem.getExpression()instanceofSubSelect){ System.out.println(indent(level)+"发现子查询:");SubSelectsubSelect =(SubSelect)exprItem.getExpression();processSelectBody(subSelect.getSelectBody(),level +1);// 递归处理子查询}}}// 处理 WHERE 子句中的子查询if(plainSelect.getWhere()!=null){ plainSelect.getWhere().accept(newExpressionVisitorAdapter(){ @Overridepublicvoidvisit(SubSelectsubSelect){ System.out.println(indent(level)+"WHERE子句中的子查询:");processSelectBody(subSelect.getSelectBody(),level +1);}});}}elseif(selectBody instanceofSetOperationList){ // 处理 UNION/INTERSECT 等集合操作SetOperationListsetOpList =(SetOperationList)selectBody;for(SelectBodybody :setOpList.getSelects()){ processSelectBody(body,level +1);}}}/** 生成缩进字符串 */privatestaticStringindent(intlevel){ return" ".repeat(level);}}
SELECT层级:0发现子查询:SELECT层级:1WHERE子句中的子查询:SELECT层级:1
【2】解析逻辑
(1)解析子查询
SELECT 列表中的列(如 (SELECT …) AS order_count)。
WHERE 条件中的 IN、EXISTS 等操作符。
FROM 子句中的派生表(如 FROM (SELECT …) AS sub)。(2)处理表达式中的子查询
plainSelect.getWhere().accept(newExpressionVisitorAdapter(){ @Overridepublicvoidvisit(SubSelectsubSelect){ // 处理子查询}});
(3)处理 UNION/INTERSECT
if(selectBody instanceofSetOperationList){ SetOperationListsetOpList =(SetOperationList)selectBody;for(SelectBodybody :setOpList.getSelects()){ processSelectBody(body,level +1);}}
- 最近发表
- 随机阅读
-
- 华硕NUC14 Pro 京东迷你主机的促销价6499元
- 3. 数据管理和Docker的持久性
- 索尼FE50mm F1.4GM 镜头,杭州消费6000元,折扣300元!
- KTC推H25Y7电子竞技显示器 支持320Hz超高刷849元
- 前端无痛作为产品UI:MasterGo AI 有助于高效设计和开发
- 有哪些犯罪游戏? 最新的犯罪游戏盘点
- 新消息《洛克人11》:两位新制作人的加盟给这个系列带来了新的活力
- 二分查找(算法详解+模板+例题)
- 小巧高效:Acer UP300
- 实现文件存储(阿里云OSS)
- Positional Encoding
- docker:环境安装
- webrtccentos7配置
- 用文心一言来理解图像内容
- 后末日游戏哪些人气高? 十大经典后末日游戏排行榜
- 华为配置 之 STP
- 什么是反乌托邦游戏? 推荐流行的反乌托邦游戏
- 沉浸式FPS大作ROG 9系列操作拉满轻松战斗
- Android最新版本的最新版本 studio没有layout文件解决方案
- AI软件下载,最火的AI人工智能软件排名(最全策略)!
- 搜索
-
- 友情链接
-