Skip to content

第二部分: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。
  • 空值判断:
    • IsNullExpression: column IS NULL 或 column IS NOT NULL
      • getLeftExpression(): column
      • isNot(): 判断是否为 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:

  1. 解析复杂 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;
  2. 提取信息:

    • 打印出主查询 FROM 子句的表名及其别名。
    • 打印出所有 JOIN 的类型和连接条件。
    • 打印出 WHERE 子句的完整表达式。
    • 提取并打印 SELECT 列表中的所有列名/表达式及其别名。特别注意子查询和 CASE 表达式。
    • 打印 GROUP BY 的所有表达式。
    • 打印 ORDER BY 的所有排序规则。
    • 打印 LIMIT 和 OFFSET 的值。
  3. (可选挑战) 解析 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 模式)打下了坚实的基础。