Appearance
第二部分:JSqlParser 核心功能详解 (Intermediate)
模块二:深入解析 SELECT 语句
SELECT 语句用于从数据库中检索数据。它的语法结构可以非常复杂,包含多个子句来指定要查询的列、数据来源、连接条件、过滤条件、分组、排序以及结果集限制等。JSqlParser 对 SELECT 语句的解析非常细致。
回顾一下,一个 com.github.jsqlparser.statement.select.Select 对象的核心是其 SelectBody。最常见的 SelectBody 类型是 PlainSelect。
1. PlainSelect 详解
com.github.jsqlparser.statement.select.PlainSelect 代表了一个不包含集合操作(如 UNION)的独立查询块。它是构成大多数 SELECT 语句的基础。
一个 PlainSelect 对象包含了构成查询的各个主要部分:
java
// 假设已经通过 CCJSqlParserUtil.parse(sql) 得到了一个 Statement 对象
// 并且 instanceof 判断为 Select 类型
Select selectStatement = (Select) statement;
if (selectStatement.getSelectBody() instanceof PlainSelect) {
PlainSelect plainSelect = (PlainSelect) selectStatement.getSelectBody();
// 现在可以访问 PlainSelect 的各个部分
// List<SelectItem> selectItems = plainSelect.getSelectItems();
// FromItem fromItem = plainSelect.getFromItem();
// List<Join> joins = plainSelect.getJoins();
// Expression where = plainSelect.getWhere();
// GroupByElement groupBy = plainSelect.getGroupBy();
// Expression having = plainSelect.getHaving();
// List<OrderByElement> orderByElements = plainSelect.getOrderByElements();
// Limit limit = plainSelect.getLimit();
// Offset offset = plainSelect.getOffset();
// Top top = plainSelect.getTop();
// Distinct distinct = plainSelect.getDistinct();
}1.1 获取查询列 (SelectItem)
SELECT 语句的核心是指定要检索哪些列。这些列由 plainSelect.getSelectItems() 返回,它是一个 List<SelectItem>。
com.github.jsqlparser.statement.select.SelectItem 是一个接口,有以下主要实现:
- SelectExpressionItem: 代表一个具体的表达式项,通常是最常见的形式。
- getExpression(): 返回一个 Expression 对象,代表被选择的列、函数或计算表达式。
- getAlias(): 返回一个 Alias 对象,代表该选择项的别名 (例如 SELECT name AS employee_name ...)。如果存在别名,alias.getName() 可以获取别名字符串。
java
// 示例:SELECT name, age AS user_age, COUNT(*) AS total FROM users
// selectItem1: SelectExpressionItem (expression=Column("name"), alias=null)
// selectItem2: SelectExpressionItem (expression=Column("age"), alias=Alias("user_age"))
// selectItem3: SelectExpressionItem (expression=Function("COUNT", AllColumns("*")), alias=Alias("total"))
List<SelectItem> selectItems = plainSelect.getSelectItems();
for (SelectItem item : selectItems) {
if (item instanceof SelectExpressionItem) {
SelectExpressionItem expItem = (SelectExpressionItem) item;
System.out.println("Expression: " + expItem.getExpression());
if (expItem.getAlias() != null) {
System.out.println("Alias: " + expItem.getAlias().getName());
}
}
// ... 其他类型的 SelectItem
}AllColumns: 代表 SELECT *,选择所有列。
// 示例:SELECT * FROM users
// selectItem: AllColumns
if (item instanceof AllColumns) {
System.out.println("SelectItem is: * (AllColumns)");
}AllTableColumns: 代表 SELECT table_alias.* 或 SELECT table_name.*,选择某个特定表的所有列。
- getTable(): 返回一个 Table 对象,表示指定的表。
// 示例:SELECT u.* FROM users u
// selectItem: AllTableColumns (table=Table("users", Alias("u")))
if (item instanceof AllTableColumns) {
AllTableColumns allTableCols = (AllTableColumns) item;
System.out.println("SelectItem is: " + allTableCols.getTable().getFullyQualifiedName() + ".*");
}
1.2 获取来源表 (FromItem)
FROM 子句指定了数据查询的来源。plainSelect.getFromItem() 返回一个 com.github.jsqlparser.statement.select.FromItem 对象。
FromItem 是一个接口,常见实现有:
- Table: 代表一个数据库表。
- getName(): 获取表名。
- getAlias(): 获取表的别名 (Alias 对象)。
- getSchemaName(): 获取模式名 (如果 SQL 中指定了,如 schema_name.table_name)。
- getFullyQualifiedName(): 获取完整的表名,可能包含模式名和表名。
FromItem fromItem = plainSelect.getFromItem();
if (fromItem instanceof Table) {
Table table = (Table) fromItem;
System.out.println("From Table: " + table.getFullyQualifiedName());
if (table.getAlias() != null) {
System.out.println("Table Alias: " + table.getAlias().getName());
}
}
- SubSelect: 代表一个子查询作为 FROM 子句的来源 (也称为派生表)。
- getSelectBody(): 返回子查询的 SelectBody。
- getAlias(): 获取子查询的别名。
// 示例: SELECT d.name FROM (SELECT id, name FROM departments WHERE location = 'US') AS d
if (fromItem instanceof SubSelect) {
SubSelect subSelect = (SubSelect) fromItem;
System.out.println("From SubSelect. Alias: " + (subSelect.getAlias() != null ? subSelect.getAlias().getName() : "None"));
// 可以进一步分析 subSelect.getSelectBody()
PlainSelect subPlainSelect = (PlainSelect) subSelect.getSelectBody();
System.out.println("SubSelect From Item: " + subPlainSelect.getFromItem());
}
- SubJoin: 代表 FROM 子句中由 JOIN 连接的更复杂的结构,本身也可能是一个 JOIN。SubJoin 通常包含一个 left (类型为 FromItem) 和一个 joinList (类型为 List<Join>)。
- LateralSubSelect: 代表 LATERAL 子查询。
1.3 处理连接查询 (Join)
当查询涉及多个表时,会使用 JOIN 子句。plainSelect.getJoins() 返回一个 List<com.github.jsqlparser.statement.select.Join>。如果查询没有 JOIN,则此列表为 null 或空。
每个 Join 对象描述了一个连接操作:
- getRightItem(): 返回 FromItem,代表被连接的表或子查询 (即 JOIN 右侧的表)。
- getOnExpression(): 返回 Expression,代表 ON 连接条件。
- getUsingColumns(): 返回 List<Column>,代表 USING 子句中指定的连接列。
- 连接类型判断方法:
- isSimple(): 是否为 FROM table1, table2 这种隐式连接。
- isInner(): 是否为 INNER JOIN。
- isLeft(): 是否为 LEFT JOIN 或 LEFT OUTER JOIN。
- isRight(): 是否为 RIGHT JOIN 或 RIGHT OUTER JOIN。
- isFull(): 是否为 FULL JOIN 或 FULL OUTER JOIN。
- isOuter(): 是否为任何一种 OUTER JOIN。
- isCross(): 是否为 CROSS JOIN。
- isNatural(): 是否为 NATURAL JOIN。
- isSemi(): 是否为 SEMI JOIN (通常用于 EXISTS 或 IN 子查询的内部表示)。
- isStraight(): 是否为 STRAIGHT_JOIN (MySQL 特定)。
// 示例: SELECT e.name, d.name FROM employees e INNER JOIN departments d ON e.dept_id = d.id
List<Join> joins = plainSelect.getJoins();
if (joins != null) {
for (Join join : joins) {
System.out.println("Join Type: " + (join.isInner() ? "INNER" : join.isLeft() ? "LEFT" : "OTHER"));
System.out.println("Right Item: " + join.getRightItem()); // 通常是一个 Table
if (join.getOnExpression() != null) {
System.out.println("ON Expression: " + join.getOnExpression());
}
if (join.getUsingColumns() != null && !join.getUsingColumns().isEmpty()) {
System.out.println("USING Columns: " + join.getUsingColumns());
}
}
}
1.4 解析 WHERE 条件 (Expression)
WHERE 子句用于过滤查询结果。plainSelect.getWhere() 返回一个 com.github.jsqlparser.expression.Expression 对象,代表整个 WHERE 条件。如果查询没有 WHERE 子句,则返回 null。
Expression 是一个非常核心的接口,有众多实现类,代表了 SQL 中各种各样的表达式。常见的有:
- 比较操作符 (Comparison Operators):
- EqualsTo (=)
- NotEqualsTo (!= 或 <>)
- GreaterThan (>)
- GreaterThanEquals (>=)
- MinorThan (<)
- MinorThanEquals (<=)
- LikeExpression (LIKE)
- RegExpMatchOperator (RLIKE, REGEXP - MySQL等)
- JsonOperator (@>, <@, ? - PostgreSQL JSON 操作符)
- 这些类通常有 getLeftExpression() 和 getRightExpression() 方法。
- 逻辑操作符 (Logical Operators):
- AndExpression (AND)
- OrExpression (OR)
- NotExpression (NOT) - 这是一个一元操作符,通过 getExpression() 获取其操作数。
- AndExpression 和 OrExpression 也是二元表达式,有 getLeftExpression() 和 getRightExpression()。
- 范围与集合操作符:
- Between: column BETWEEN val1 AND val2
- getLeftExpression(): column
- getBetweenExpressionStart(): val1
- getBetweenExpressionEnd(): val2
- InExpression: column IN (val1, val2, ...) 或 column IN (subquery)
- getLeftExpression(): column (可选,如果 getLeftItemsList 不为空则此项为 null)
- getLeftItemsList(): ItemsList,用于 (col1, col2) IN ((val1, val2), ...) 这种元组比较。
- getRightItemsList(): ItemsList,代表 IN 后面的值列表或子查询。
- isNot(): 判断是否为 NOT IN。
- Between: column BETWEEN val1 AND val2
- 空值判断:
- IsNullExpression: column IS NULL 或 column IS NOT NULL
- getLeftExpression(): column
- isNot(): 判断是否为 IS NOT NULL。
- IsNullExpression: column IS NULL 或 column IS NOT NULL
- 列引用:
- Column: 代表一个列名,如 users.id 或 name。
- 字面量:
- LongValue, DoubleValue, StringValue, DateValue, TimeValue, TimestampValue, NullValue。
- 函数调用:
- Function: 如 WHERE UPPER(name) = 'ADMIN'。
- 子查询:
- SubSelect: 当子查询用在表达式中时,例如 WHERE col = (SELECT MAX(id) FROM other_table)。
- ExistsExpression: EXISTS (subquery) 或 NOT EXISTS (subquery)。
遍历 WHERE 条件的例子 (通常使用 Visitor 模式更佳,此处为简化说明):
java
Expression whereCondition = plainSelect.getWhere();
if (whereCondition != null) {
System.out.println("WHERE Clause: " + whereCondition.toString()); // 打印整个条件
// 简单示例:检查是否为 AndExpression
if (whereCondition instanceof AndExpression) {
AndExpression andExpr = (AndExpression) whereCondition;
Expression left = andExpr.getLeftExpression();
Expression right = andExpr.getRightExpression();
System.out.println(" Left part of AND: " + left);
System.out.println(" Right part of AND: " + right);
// 如果左边是 EqualsTo
if (left instanceof EqualsTo) {
EqualsTo eq = (EqualsTo) left;
System.out.println(" Equals: " + eq.getLeftExpression() + " = " + eq.getRightExpression());
if (eq.getLeftExpression() instanceof Column) {
System.out.println(" Filtering on column: " + ((Column)eq.getLeftExpression()).getColumnName());
}
}
} else if (whereCondition instanceof EqualsTo) {
// ... 处理单个 EqualsTo 条件
}
}}
// ... 可以继续判断其他 Expression 类型
}
*注意:直接通过 instanceof 递归判断 Expression 的结构会非常复杂。后续我们会学习使用 **Visitor 模式** 来优雅地遍历和处理复杂的表达式树。*
#### **1.5 解析 GROUP BY (GroupByElement) 和 HAVING 子句**
* **GROUP BY**: 用于将结果集按一个或多个列进行分组。
* plainSelect.getGroupBy() 返回一个 com.github.jsqlparser.statement.select.GroupByElement 对象。
* getGroupByExpressions(): 返回 List\<Expression\>,代表 GROUP BY 后面的列或表达式。
* getGroupingSets(): 用于处理 GROUPING SETS 子句。
GroupByElement groupBy \= plainSelect.getGroupBy();
if (groupBy \!= null) {
List\<Expression\> groupByExprs \= groupBy.getGroupByExpressions();
System.out.println("GROUP BY expressions:");
for (Expression expr : groupByExprs) {
System.out.println(" \- " \+ expr); // 通常是 Column 对象
}
}
* **HAVING**: 用于在 GROUP BY 分组后对分组结果进行过滤。
* plainSelect.getHaving() 返回一个 Expression 对象,代表 HAVING 条件。其结构与 WHERE 条件类似。
Expression havingCondition \= plainSelect.getHaving();
if (havingCondition \!= null) {
System.out.println("HAVING Clause: " \+ havingCondition);
// 分析 havingCondition 的方式与分析 WHERE 条件类似
}
#### **1.6 解析 ORDER BY (OrderByElement)**
ORDER BY 子句用于对结果集进行排序。
plainSelect.getOrderByElements() 返回一个 List\<com.github.jsqlparser.statement.select.OrderByElement\>。
每个 OrderByElement 对象描述了一个排序规则:
* getExpression(): 返回 Expression,代表排序依据的列或表达式。
* isAsc(): 判断是否为升序 (ASC)。默认为 true。
* isAscDescPresent(): 判断 SQL 中是否显式指定了 ASC 或 DESC。
* getNullOrdering(): 获取 NULLS FIRST 或 NULLS LAST 的设置 (NullOrdering 枚举)。
List\<OrderByElement\> orderByElements \= plainSelect.getOrderByElements();
if (orderByElements \!= null) {
System.out.println("ORDER BY elements:");
for (OrderByElement orderBy : orderByElements) {
System.out.print(" \- Expression: " \+ orderBy.getExpression());
System.out.print(", Ascending: " \+ orderBy.isAsc());
if (orderBy.getNullOrdering() \!= null) {
System.out.print(", Nulls: " \+ orderBy.getNullOrdering());
}
System.out.println();
}
}
#### **1.7 解析 LIMIT, OFFSET, TOP**
这些子句用于限制返回结果集的行数。
* **LIMIT**: 通常用于 MySQL, PostgreSQL。
* plainSelect.getLimit() 返回一个 com.github.jsqlparser.statement.select.Limit 对象。
* getRowCount(): 返回 Expression,代表 LIMIT 的行数。
* getOffset(): 返回 Expression,代表 LIMIT offset, count 中的 offset (如果 isOffsetJdbcParameter() 或 getOffset() 非 null)。
* isLimitNull(): 是否为 LIMIT NULL。
* isLimitAll(): 是否为 LIMIT ALL。
Limit limit \= plainSelect.getLimit();
if (limit \!= null) {
System.out.println("LIMIT RowCount: " \+ limit.getRowCount());
if (limit.getOffset() \!= null) { // 注意: LIMIT m,n 中的 m
System.out.println("LIMIT Offset: " \+ limit.getOffset());
}
}
* **OFFSET**: 通常用于 PostgreSQL, SQL Standard (配合 FETCH FIRST)。
* plainSelect.getOffset() 返回一个 com.github.jsqlparser.statement.select.Offset 对象。
* getOffset(): 返回 Expression,代表 OFFSET 的行数。
* getOffsetParam(): 返回字符串,如 ROWS 或 ROW。
Offset offset \= plainSelect.getOffset();
if (offset \!= null) {
System.out.println("OFFSET Value: " \+ offset.getOffset());
}
JSqlParser 还支持 FETCH FIRST N ROWS ONLY (通过 Fetch 对象 plainSelect.getFetch())。
* **TOP**: 通常用于 SQL Server。
* plainSelect.getTop() 返回一个 com.github.jsqlparser.statement.select.Top 对象。
* getExpression(): 返回 Expression,代表 TOP 的数量。
* isPercentage(): 判断是否为 TOP N PERCENT。
Top top \= plainSelect.getTop();
if (top \!= null) {
System.out.println("TOP Expression: " \+ top.getExpression());
if (top.isPercentage()) {
System.out.println(" (Percentage)");
}
}
#### **1.8 DISTINCT / UNIQUE**
plainSelect.getDistinct() 返回一个 com.github.jsqlparser.statement.select.Distinct 对象,如果查询中使用了 SELECT DISTINCT ... 或 SELECT UNIQUE ...。否则返回 null。
* getOnSelectItems(): 返回 List\<SelectItem\>,用于 DISTINCT ON (...) (PostgreSQL 特定)。
Distinct distinct \= plainSelect.getDistinct();
if (distinct \!= null) {
System.out.println("Query uses DISTINCT.");
if (distinct.getOnSelectItems() \!= null && \!distinct.getOnSelectItems().isEmpty()) {
System.out.println(" DISTINCT ON: " \+ distinct.getOnSelectItems());
}
}
### **2\. 处理子查询 (SubSelect)**
子查询 (SubQuery 或 SubSelect) 是嵌套在其他 SQL 语句中的查询。JSqlParser 中的 com.github.jsqlparser.statement.select.SubSelect 类代表了子查询。
子查询可以出现在多个地方:
* **FROM 子句中 (派生表):**
* 前面已经提到,FromItem 可以是 SubSelect 类型。
* SubSelect subSelectFrom \= (SubSelect) plainSelect.getFromItem();
* SelectBody innerSelectBody \= subSelectFrom.getSelectBody();
* Alias alias \= subSelectFrom.getAlias();
* **WHERE 条件中:**
* **标量子查询 (Scalar Subquery):** 返回单个值的子查询,可以用在比较表达式中。
SELECT name FROM employees WHERE salary \= (SELECT MAX(salary) FROM employees WHERE department\_id \= 10);
此时,(SELECT MAX(salary) ...) 会被解析为一个 SubSelect 对象,作为 EqualsTo 表达式的右操作数。
* **IN / NOT IN 子查询:**
SELECT name FROM employees WHERE department\_id IN (SELECT id FROM departments WHERE location \= 'New York');
\`\`\`InExpression\` 的 \`getRightItemsList()\` 会返回一个 \`SubSelect\` 对象。
* **EXISTS / NOT EXISTS 子查询:**
SELECT name FROM departments d WHERE EXISTS (SELECT 1 FROM employees e WHERE e.department\_id \= d.id);
\`\`\`ExistsExpression\` 的 \`getRightExpression()\` 会是一个 \`SubSelect\` 对象。
* **比较量词 (ANY, ALL, SOME):**
SELECT name FROM employees WHERE salary \> ANY (SELECT bonus FROM special\_bonuses);
这里 (SELECT bonus ...) 也是一个 SubSelect,通常与 AnyComparisonExpression 或 AllComparisonExpression 配合使用。
* **SELECT 列中 (标量子查询):**
SELECT name, (SELECT COUNT(\*) FROM orders o WHERE o.customer\_id \= c.id) AS order\_count FROM customers c;
此时,(SELECT COUNT(\*) ...) 会被解析为一个 SubSelect 对象,作为 SelectExpressionItem 的 Expression。
**访问子查询内容:**
一旦你获得了一个 SubSelect 对象,就可以通过 getSelectBody() 方法获取其内部的 SelectBody (通常是 PlainSelect),然后就可以像分析主查询一样分析子查询的各个部分了。
// 假设 expr 是一个 Expression 对象
if (expr instanceof SubSelect) {
SubSelect subQuery \= (SubSelect) expr;
System.out.println("Found a SubSelect in expression: " \+ subQuery);
SelectBody subBody \= subQuery.getSelectBody();
if (subBody instanceof PlainSelect) {
PlainSelect subPlain \= (PlainSelect) subBody;
System.out.println(" SubSelect's FromItem: " \+ subPlain.getFromItem());
// ... 进一步分析子查询
}
}
### **3\. 解析函数和复杂表达式**
SQL 表达式的解析是 JSqlParser 的核心功能之一,由 com.github.jsqlparser.expression.Expression 接口及其众多子类实现。
#### **函数 (Function)**
com.github.jsqlparser.expression.Function 类代表 SQL 函数调用。
* getName(): 获取函数名 (字符串,例如 COUNT, SUBSTRING, MAX)。
* getParameters(): 返回 ExpressionList 对象,其中包含函数的参数列表 (List\<Expression\>)。如果函数没有参数 (如 NOW()),则为 null 或空。
* isAllColumns(): 特殊用于 COUNT(\*),如果参数是 \* 则返回 true。
* isDistinct(): 用于聚合函数,如 COUNT(DISTINCT name)。
* getKeep(): 用于 KEEP (DENSE\_RANK FIRST/LAST ORDER BY ...) 这类分析函数。
* getOrderByElements(): 用于某些函数内部的 ORDER BY 子句 (如某些聚合函数或窗口函数)。
* getWindow(): 如果是窗口函数,返回 WindowElement 对象。
// 示例:SELECT COUNT(DISTINCT department\_id), SUBSTRING(name, 1, 3\) FROM employees
// 假设 expr 是 SelectExpressionItem.getExpression()
if (expr instanceof Function) {
Function func \= (Function) expr;
System.out.println("Function Name: " \+ func.getName());
if (func.isDistinct()) {
System.out.println(" (DISTINCT)");
}
if (func.getParameters() \!= null) {
System.out.println(" Parameters: " \+ func.getParameters().getExpressions());
for (Expression paramExpr : func.getParameters().getExpressions()) {
// 进一步分析参数表达式
if (paramExpr instanceof Column) {
System.out.println(" Param Column: " \+ ((Column)paramExpr).getColumnName());
}
}
} else if (func.isAllColumns()) { // for COUNT(\*)
System.out.println(" Parameter: \*");
}
}
#### **CASE 表达式 (CaseExpression, WhenClause)**
com.github.jsqlparser.expression.operators.conditional.CaseExpression 代表 CASE ... END 表达式。
* getSwitchExpression(): 对于 CASE col WHEN ... 形式,返回 col 这个 Expression。对于 CASE WHEN cond1 THEN ... 形式,此项为 null。
* getWhenClauses(): 返回 List\<WhenClause\>,代表所有的 WHEN ... THEN ... 部分。
* getElseExpression(): 返回 Expression,代表 ELSE 部分的表达式 (如果存在)。
com.github.jsqlparser.expression.WhenClause 代表 WHEN condition THEN result。
* getWhenExpression(): 返回 WHEN 后面的条件 Expression。
* getThenExpression(): 返回 THEN 后面的结果 Expression。
// 示例: SELECT CASE WHEN score \>= 90 THEN 'A' WHEN score \>= 80 THEN 'B' ELSE 'C' END FROM students
if (expr instanceof CaseExpression) {
CaseExpression caseExpr \= (CaseExpression) expr;
if (caseExpr.getSwitchExpression() \!= null) {
System.out.println("CASE Switch Expression: " \+ caseExpr.getSwitchExpression());
}
for (WhenClause whenClause : caseExpr.getWhenClauses()) {
System.out.println(" WHEN " \+ whenClause.getWhenExpression() \+ " THEN " \+ whenClause.getThenExpression());
}
if (caseExpr.getElseExpression() \!= null) {
System.out.println(" ELSE " \+ caseExpr.getElseExpression());
}
}
#### **其他复杂表达式**
* **算术表达式:** Addition, Subtraction, Multiplication, Division, Modulo。它们都是 BinaryExpression 的子类,有 getLeftExpression() 和 getRightExpression()。
* **字符串连接:** Concat (例如 expr1 || expr2)。
* **类型转换:** CastExpression (CAST(expr AS type) 或 CONVERT(expr, type))。
* getLeftExpression(): 要转换的表达式。
* getType(): ColDataType 对象,表示目标类型。
* **括号:** Parenthesis ((expr)),通过 getExpression() 获取内部表达式。
* **参数占位符:** JdbcParameter (?), JdbcNamedParameter (:name)。
遍历和理解这些嵌套的 Expression 对象是使用 JSqlParser 的关键技能。同样,Visitor 模式是处理这种树形结构的最佳方式。
### **4\. 集合操作 (SetOperationList)**
当多个 SELECT 语句通过 UNION, UNION ALL, INTERSECT, EXCEPT (或 MINUS) 连接时,SelectBody 的类型会是 com.github.jsqlparser.statement.select.SetOperationList。
SetOperationList 包含:
* getSelects(): 返回 List\<SelectBody\>。这个列表包含了所有参与集合操作的 SELECT 语句的 SelectBody (通常是 PlainSelect 或嵌套的 SetOperationList)。列表的顺序与 SQL 语句中的顺序一致。
* getOperations(): 返回 List\<SetOperation\>。这个列表包含了连接各个 SelectBody 的具体集合操作。操作的数量比 SelectBody 的数量少一个。
* SetOperation 是一个接口,其实现类包括 UnionOp, IntersectOp, ExceptOp, MinusOp。
* UnionOp 有一个 isAll() 方法来区分 UNION 和 UNION ALL。
* getOrderByElements(): 集合操作的结果集可以有自己的 ORDER BY 子句。
* getLimit(), getOffset(), getFetch(): 集合操作的结果集也可以有 LIMIT, OFFSET, FETCH。
```java
// 示例: SELECT name FROM table1 WHERE id > 10 UNION ALL SELECT name FROM table2 WHERE id < 5 ORDER BY name LIMIT 10
if (selectStatement.getSelectBody() instanceof SetOperationList) {
SetOperationList setOpList = (SetOperationList) selectStatement.getSelectBody();
List<SelectBody> selects = setOpList.getSelects();
List<SetOperation> operations = setOpList.getOperations();
for (int i = 0; i < selects.size(); i++) {
System.out.println("Query Block " + (i + 1) + ":");
SelectBody subBody = selects.get(i);
// 通常 subBody 是 PlainSelect,可以进一步分析
if (subBody instanceof PlainSelect) {
PlainSelect subPlain = (PlainSelect) subBody;
System.out.println(" From: " + subPlain.getFromItem());
System.out.println(" Where: " + subPlain.getWhere());
}
if (i < operations.size()) {
SetOperation op = operations.get(i);
if (op instanceof UnionOp) {
System.out.println("Operation: UNION" + (((UnionOp) op).isAll() ? " ALL" : ""));
} else if (op instanceof IntersectOp) {
System.out.println("Operation: INTERSECT");
} else if (op instanceof ExceptOp) {
System.out.println("Operation: EXCEPT");
} // ... etc.
}
}
if (setOpList.getOrderByElements() != null) {
System.out.println("Overall ORDER BY: " + setOpList.getOrderByElements());
}
if (setOpList.getLimit() != null) {
System.out.println("Overall LIMIT: " + setOpList.getLimit().getRowCount());
}
}动手实验 2:
解析复杂 SELECT 语句:
- 编写 Java 代码,解析以下 SQL 语句:
SELECT
e.emp_name AS EmployeeName,
d.dept_name AS DepartmentName,
(SELECT COUNT(*) FROM projects p WHERE p.resp_emp_id = e.emp_id AND p.status = 'Active') AS ActiveProjects,
CASE
WHEN e.salary >= 100000 THEN 'High'
WHEN e.salary >= 60000 AND e.salary < 100000 THEN 'Medium'
ELSE 'Low'
END AS SalaryGrade
FROM
employees e
LEFT JOIN
departments d ON e.department_id = d.dept_id
WHERE
e.hire_date > '2020-01-01' AND d.location IN ('New York', 'London')
GROUP BY
e.emp_id, e.emp_name, d.dept_name, e.salary -- emp_id for uniqueness
HAVING
COUNT(e.emp_id) > 0 -- Simplified HAVING for example
ORDER BY
DepartmentName ASC, ActiveProjects DESC
LIMIT 10 OFFSET 5;
- 编写 Java 代码,解析以下 SQL 语句:
提取信息:
- 打印出主查询 FROM 子句的表名及其别名。
- 打印出所有 JOIN 的类型和连接条件。
- 打印出 WHERE 子句的完整表达式。
- 提取并打印 SELECT 列表中的所有列名/表达式及其别名。特别注意子查询和 CASE 表达式。
- 打印 GROUP BY 的所有表达式。
- 打印 ORDER BY 的所有排序规则。
- 打印 LIMIT 和 OFFSET 的值。
(可选挑战) 解析 UNION 查询:
编写代码解析以下 SQL:
(SELECT id, name, 'staff' as type FROM staff_members WHERE active = 1)
UNION ALL
(SELECT id, name, 'contractor' as type FROM contractors WHERE end_date > CURRENT_DATE)
ORDER BY name;识别出这是一个 UNION ALL 操作。
分别打印出两个子查询的 FROM 表和 WHERE 条件。
打印出最终的 ORDER BY 子句。
模块二小结:
在本模块中,我们深入学习了如何使用 JSqlParser 解析 SELECT 语句的各个组成部分:
- 通过 PlainSelect 对象访问查询列 (SelectItem)、来源 (FromItem)、连接 (Join)、过滤条件 (WHERE)、分组 (GROUP BY)、分组后过滤 (HAVING)、排序 (ORDER BY) 以及结果限制 (LIMIT, OFFSET, TOP, DISTINCT)。
- 理解了 Expression 对象在表示 WHERE, HAVING 条件以及各种计算和函数调用中的核心作用。
- 学习了如何识别和分析子查询 (SubSelect) 在 SELECT 语句不同位置的应用。
- 掌握了如何解析函数调用 (Function) 和 CASE 表达式。
- 了解了如何处理由 UNION, INTERSECT, EXCEPT 等连接的集合操作 (SetOperationList)。
通过对 SELECT 语句的详细解析,我们为后续学习更高级的 JSqlParser 功能(如 Visitor 模式)打下了坚实的基础。