druid 数据源【七、子查询】

Druid 各种子查询 详解

一、先记牢:Druid 中子查询的真实类型(必背,无虚构类)

子查询的核心区别在于「出现位置」和「作用」,对应不同的 Druid 类,以下是完整对应关系,直接记熟即可避坑:

子查询位置 SQL 写法示例 对应 Druid 类 所属父类(类型) 核心作用
WHERE 子句(条件中) id IN (SELECT user_id FROM order) SQLInSubQueryExpr SQLExpr(表达式) 判断字段是否在子查询结果中
EXISTS (SELECT 1 FROM order WHERE user_id = id) SQLExistsExpr SQLExpr(表达式) 判断子查询是否有结果
age > ANY (SELECT age FROM vip) SQLAnyExpr SQLExpr(表达式) 判断字段是否大于子查询中任意一个结果
age > ALL (SELECT age FROM vip) SQLAllExpr SQLExpr(表达式) 判断字段是否大于子查询中所有结果
age = (SELECT MAX(age) FROM user) SQLBinaryOpExpr(嵌套 SQLQueryExpr) SQLExpr(表达式) 用比较运算符(=、>、< 等)连接子查询
FROM 子句(表来源) FROM (SELECT id, name FROM user) AS t SQLSubqueryTableSource SQLTableSource(表来源) 子查询作为派生表,供主查询关联
SELECT 列表(字段中) SELECT id, (SELECT name FROM user WHERE id=1) FROM order SQLQueryExpr SQLExpr(表达式) 标量子查询,返回单个值作为查询列

二、环境准备(必配,否则解析失败)

2.1 Maven 依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.20</version> <!-- 稳定版本,推荐使用 -->
</dependency>

2.2 通用导入包

所有示例均需导入以下包,统一复制即可:

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLSelect;
import com.alibaba.druid.sql.ast.expr.*;
import com.alibaba.druid.sql.ast.statement.*;
import com.alibaba.druid.util.JdbcConstants;
import java.util.List;

三、分场景实战:所有子查询解析 + 使用示例

按「使用频率」排序,每个场景均提供「SQL 示例 + 解析代码 + 结果说明」,可直接复制运行,重点补充「比较运算符子查询」细节。

场景 1:WHERE 后的子查询(最常用,5 种细分场景)

WHERE 子查询的核心是「作为条件判断」,所有相关类均继承自 SQLExpr,解析时需先获取 WHERE 表达式,再判断具体类型。

1.1 IN 子查询:id IN (SELECT ...)(对应 SQLInSubQueryExpr)

最常用的子查询场景,判断字段值是否在子查询返回的结果集中。

示例:解析 IN 子查询

public class SubQueryDemo1 {
    public static void main(String[] args) {
        // SQL:WHERE 后 IN 子查询
        String sql = "SELECT id, name FROM user WHERE id IN (SELECT user_id FROM `order` WHERE status = 1)";
        
        // 1. 解析 SQL 语句
        SQLSelectStatement stmt = (SQLSelectStatement) SQLUtils.parseSingleStatement(sql, JdbcConstants.MYSQL);
        // 2. 获取主查询块
        SQLSelectQueryBlock mainBlock = stmt.getSelect().getQueryBlock();
        // 3. 获取 WHERE 条件表达式
        SQLExpr whereExpr = mainBlock.getWhere();
        
        // 4. 判断是否为 IN 子查询
        if (whereExpr instanceof SQLInSubQueryExpr) {
            SQLInSubQueryExpr inSubQuery = (SQLInSubQueryExpr) whereExpr;
            
            // 提取左侧字段(id)
            SQLExpr leftCol = inSubQuery.getExpr();
            // 提取子查询对象
            SQLSelect subSelect = inSubQuery.getSubQuery();
            // 提取子查询的查询块(方便进一步解析子查询的条件、表等)
            SQLSelectQueryBlock subBlock = subSelect.getQueryBlock();
            
            // 打印结果
            System.out.println("左侧判断字段:" + SQLUtils.toSQLString(leftCol));
            System.out.println("IN 子查询完整 SQL:" + SQLUtils.toSQLString(subSelect));
            System.out.println("子查询关联的表:" + SQLUtils.toSQLString(subBlock.getFrom()));
            System.out.println("子查询条件:" + SQLUtils.toSQLString(subBlock.getWhere()));
        }
    }
}

运行结果

左侧判断字段:id
IN 子查询完整 SQL:SELECT user_id FROM `order` WHERE status = 1
子查询关联的表:`order`
子查询条件:status = 1

1.2 EXISTS 子查询:EXISTS (SELECT ...)(对应 SQLExistsExpr)

判断子查询是否返回结果(无需关注具体结果,只看是否有数据),支持 NOT EXISTS。

示例:解析 EXISTS 子查询 + 改写(添加子查询条件)

public class SubQueryDemo2 {
    public static void main(String[] args) {
        // SQL:WHERE 后 EXISTS 子查询(带关联条件)
        String sql = "SELECT id, name FROM user u WHERE EXISTS (SELECT 1 FROM `order` o WHERE o.user_id = u.id)";
        
        SQLSelectStatement stmt = (SQLSelectStatement) SQLUtils.parseSingleStatement(sql, JdbcConstants.MYSQL);
        SQLExpr whereExpr = stmt.getSelect().getQueryBlock().getWhere();
        
        // 判断是否为 EXISTS 子查询
        if (whereExpr instanceof SQLExistsExpr) {
            SQLExistsExpr existsExpr = (SQLExistsExpr) whereExpr;
            
            // 提取子查询
            SQLSelect subSelect = existsExpr.getSubQuery();
            SQLSelectQueryBlock subBlock = subSelect.getQueryBlock();
            
            System.out.println("原 EXISTS 子查询:" + SQLUtils.toSQLString(subSelect));
            System.out.println("是否为 NOT EXISTS:" + existsExpr.isNot());
            
            // 改写:给子查询添加条件(order.create_time > '2024-01-01')
            SQLExpr newCond = SQLUtils.toSQLExpr("o.create_time > '2024-01-01'", JdbcConstants.MYSQL);
            subBlock.addWhere(newCond);
            
            // 输出改写后的完整 SQL
            System.out.println("改写后完整 SQL:" + SQLUtils.toSQLString(stmt));
        }
    }
}

运行结果

原 EXISTS 子查询:SELECT 1 FROM `order` o WHERE o.user_id = u.id
是否为 NOT EXISTS:false
改写后完整 SQL:SELECT id, name FROM user u WHERE EXISTS (SELECT 1 FROM `order` o WHERE o.user_id = u.id AND o.create_time > '2024-01-01')

1.3 ANY / ALL 子查询(对应 SQLAnyExpr / SQLAllExpr)

用于「字段与子查询结果集的批量比较」:

  • ANY:字段只需满足子查询中「任意一个」结果即可

  • ALL:字段需满足子查询中「所有」结果才可

示例:解析 ANY / ALL 子查询

public class SubQueryDemo3 {
    public static void main(String[] args) {
        // 1. ANY 子查询示例
        String sql1 = "SELECT id, name FROM user WHERE age > ANY (SELECT age FROM vip WHERE level = 1)";
        // 2. ALL 子查询示例
        String sql2 = "SELECT id, name FROM user WHERE age > ALL (SELECT age FROM vip WHERE level = 1)";
        
        // 解析 ANY 子查询
        parseAnyAllSubQuery(sql1);
        System.out.println("--------------------------");
        // 解析 ALL 子查询
        parseAnyAllSubQuery(sql2);
    }
    
    private static void parseAnyAllSubQuery(String sql) {
        SQLSelectStatement stmt = (SQLSelectStatement) SQLUtils.parseSingleStatement(sql, JdbcConstants.MYSQL);
        SQLExpr whereExpr = stmt.getSelect().getQueryBlock().getWhere();
        
        if (whereExpr instanceof SQLAnyExpr) {
            SQLAnyExpr anyExpr = (SQLAnyExpr) whereExpr;
            System.out.println("子查询类型:ANY 子查询");
            System.out.println("子查询 SQL:" + SQLUtils.toSQLString(anyExpr.getSubQuery()));
        } else if (whereExpr instanceof SQLAllExpr) {
            SQLAllExpr allExpr = (SQLAllExpr) whereExpr;
            System.out.println("子查询类型:ALL 子查询");
            System.out.println("子查询 SQL:" + SQLUtils.toSQLString(allExpr.getSubQuery()));
        }
    }
}

运行结果

子查询类型:ANY 子查询
子查询 SQL:SELECT age FROM vip WHERE level = 1
--------------------------
子查询类型:ALL 子查询
子查询 SQL:SELECT age FROM vip WHERE level = 1

1.4 比较运算符子查询(重点补充,对应 SQLBinaryOpExpr + SQLQueryExpr)

最容易被忽略的子查询场景:用「=、>、<、>=、<=、!=」等比较运算符,直接连接字段和子查询(子查询需返回单个值,即标量子查询)。

核心逻辑:比较运算符子查询本质是「二元运算表达式(SQLBinaryOpExpr)」,右侧是「标量子查询(SQLQueryExpr)」,解析时需先获取二元运算的右侧表达式,再判断是否为标量子查询。

常见 SQL 示例

  • age = (SELECT MAX(age) FROM user) → 等于子查询结果(最大值)

  • create_time > (SELECT MIN(create_time) FROM order) → 大于子查询结果(最小值)

  • salary <= (SELECT AVG(salary) FROM dept WHERE dept_id = 10) → 小于等于子查询结果(平均值)

示例:解析 + 改写比较运算符子查询

public class SubQueryDemo4 {
    public static void main(String[] args) {
        // SQL:比较运算符子查询(= 连接,子查询返回单个值)
        String sql = "SELECT id, name FROM user WHERE age = (SELECT MAX(age) FROM user WHERE dept_id = 3)";
        
        SQLSelectStatement stmt = (SQLSelectStatement) SQLUtils.parseSingleStatement(sql, JdbcConstants.MYSQL);
        SQLSelectQueryBlock mainBlock = stmt.getSelect().getQueryBlock();
        SQLExpr whereExpr = mainBlock.getWhere();
        
        // 关键:比较运算符子查询是 SQLBinaryOpExpr(二元运算)
        if (whereExpr instanceof SQLBinaryOpExpr) {
            SQLBinaryOpExpr binaryOp = (SQLBinaryOpExpr) whereExpr;
            
            // 左侧:字段(age)
            SQLExpr leftExpr = binaryOp.getLeft();
            // 右侧:子查询(SELECT MAX(age) ...),类型是 SQLQueryExpr
            SQLExpr rightExpr = binaryOp.getRight();
            
            // 判断右侧是否为标量子查询
            if (rightExpr instanceof SQLQueryExpr) {
                SQLQueryExpr queryExpr = (SQLQueryExpr) rightExpr;
                SQLSelect subSelect = queryExpr.getSubQuery();
                SQLSelectQueryBlock subBlock = subSelect.getQueryBlock();
                
                // 打印解析结果
                System.out.println("比较运算符:" + binaryOp.getOperator());
                System.out.println("左侧字段:" + SQLUtils.toSQLString(leftExpr));
                System.out.println("右侧标量子查询:" + SQLUtils.toSQLString(subSelect));
                System.out.println("子查询条件:" + SQLUtils.toSQLString(subBlock.getWhere()));
                
                // 改写:修改子查询条件(dept_id 从 3 改为 4)
                subBlock.setWhere(SQLUtils.toSQLExpr("dept_id = 4", JdbcConstants.MYSQL));
                System.out.println("改写后完整 SQL:" + SQLUtils.toSQLString(stmt));
            }
        }
    }
}

运行结果

比较运算符:Equality(=)
左侧字段:age
右侧标量子查询:SELECT MAX(age) FROM user WHERE dept_id = 3
子查询条件:dept_id = 3
改写后完整 SQL:SELECT id, name FROM user WHERE age = (SELECT MAX(age) FROM user WHERE dept_id = 4)

场景 2:FROM 后的子查询(派生表,对应 SQLSubqueryTableSource)

核心特点:子查询作为「表来源」,必须给子查询起别名(否则 SQL 语法错误),对应 Druid 类是 SQLSubqueryTableSource,不属于 SQLExpr,属于 SQLTableSource(和普通表、联表的父类一致)。

示例:解析 FROM 子查询 + 提取子查询信息 + 改写子查询

public class SubQueryDemo5 {
    public static void main(String[] args) {
        // SQL:FROM 后子查询(派生表),带别名 t
        String sql = "SELECT t.id, t.name, t.age FROM (SELECT id, name, age FROM user WHERE age > 18) AS t WHERE t.age < 30";
        
        SQLSelectStatement stmt = (SQLSelectStatement) SQLUtils.parseSingleStatement(sql, JdbcConstants.MYSQL);
        SQLSelectQueryBlock mainBlock = stmt.getSelect().getQueryBlock();
        
        // 获取 FROM 后的表来源(子查询派生表)
        SQLTableSource tableSource = mainBlock.getFrom();
        
        // 判断是否为 FROM 子查询
        if (tableSource instanceof SQLSubqueryTableSource) {
            SQLSubqueryTableSource subTable = (SQLSubqueryTableSource) tableSource;
            
            // 提取关键信息
            String alias = subTable.getAlias(); // 子查询别名 t
            SQLSelect subSelect = subTable.getSelect(); // 子查询对象
            SQLSelectQueryBlock subBlock = subSelect.getQueryBlock(); // 子查询的查询块
            
            System.out.println("FROM 子查询别名:" + alias);
            System.out.println("FROM 子查询完整 SQL:" + SQLUtils.toSQLString(subSelect));
            System.out.println("子查询查询列:" + SQLUtils.toSQLString(subBlock.getSelectList()));
            System.out.println("子查询条件:" + SQLUtils.toSQLString(subBlock.getWhere()));
            
            // 改写:给子查询添加条件(name LIKE '%张%')
            SQLExpr newCond = SQLUtils.toSQLExpr("name LIKE '%张%'", JdbcConstants.MYSQL);
            subBlock.addWhere(newCond);
            
            System.out.println("改写后完整 SQL:" + SQLUtils.toSQLString(stmt));
        }
    }
}

运行结果

FROM 子查询别名:t
FROM 子查询完整 SQL:SELECT id, name, age FROM user WHERE age > 18
子查询查询列:id, name, age
子查询条件:age > 18
改写后完整 SQL:SELECT t.id, t.name, t.age FROM (SELECT id, name, age FROM user WHERE age > 18 AND name LIKE '%张%') AS t WHERE t.age < 30

场景 3:SELECT 列表中的标量子查询(对应 SQLQueryExpr)

核心特点:子查询作为「查询列」,返回单个值(标量),对应 Druid 类是 SQLQueryExpr,属于 SQLExpr,通常嵌套在 SQLSelectItem 中。

示例:解析 SELECT 标量子查询 + 遍历所有查询列

public class SubQueryDemo6 {
    public static void main(String[] args) {
        // SQL:SELECT 列表中包含 2 个标量子查询
        String sql = "SELECT " +
                "id, " +
                "(SELECT name FROM user WHERE user.id = order.user_id) AS user_name, " +
                "(SELECT dept_name FROM dept WHERE dept.id = user.dept_id) AS dept_name " +
                "FROM `order`";
        
        SQLSelectStatement stmt = (SQLSelectStatement) SQLUtils.parseSingleStatement(sql, JdbcConstants.MYSQL);
        SQLSelectQueryBlock mainBlock = stmt.getSelect().getQueryBlock();
        
        // 遍历所有查询列,识别标量子查询
        List<SQLSelectItem> selectItems = mainBlock.getSelectList();
        for (int i = 0; i < selectItems.size(); i++) {
            SQLSelectItem item = selectItems.get(i);
            SQLExpr expr = item.getExpr();
            String columnAlias = item.getAlias(); // 查询列别名
            
            System.out.println("第 " + (i+1) + " 列:");
            System.out.println("  列别名:" + (columnAlias == null ? "无" : columnAlias));
            
            // 判断是否为标量子查询
            if (expr instanceof SQLQueryExpr) {
                SQLQueryExpr queryExpr = (SQLQueryExpr) expr;
                SQLSelect subSelect = queryExpr.getSubQuery();
                System.out.println("  类型:标量子查询");
                System.out.println("  子查询 SQL:" + SQLUtils.toSQLString(subSelect));
            } else {
                System.out.println("  类型:普通字段");
                System.out.println("  字段名:" + SQLUtils.toSQLString(expr));
            }
            System.out.println("--------------------------");
        }
    }
}

运行结果

第 1 列:
  列别名:无
  类型:普通字段
  字段名:id
--------------------------
第 2 列:
  列别名:user_name
  类型:标量子查询
  子查询 SQL:SELECT name FROM user WHERE user.id = order.user_id
--------------------------
第 3 列:
  列别名:dept_name
  类型:标量子查询
  子查询 SQL:SELECT dept_name FROM dept WHERE dept.id = user.dept_id
--------------------------

四、高级实战:子查询通用解析工具类(企业级,直接复制使用)

封装通用方法,支持「自动识别所有子查询类型、提取子查询信息、递归解析多层子查询」,适配所有场景,可直接集成到项目中。

import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.visitor.SQLASTVisitorAdapter;

/**
 * Druid 子查询通用解析工具类
 * 支持:所有子查询类型识别、信息提取、多层子查询递归解析
 */
public class SubQueryUtils {

    /**
     * 解析 SQL 中的所有子查询
     */
    public static void parseAllSubQueries(String sql) {
        SQLSelectStatement stmt = (SQLSelectStatement) SQLUtils.parseSingleStatement(sql, JdbcConstants.MYSQL);
        // 用 Visitor 模式遍历整个 AST,识别所有子查询
        stmt.accept(new SQLASTVisitorAdapter() {
            @Override
            public boolean visit(SQLInSubQueryExpr x) {
                System.out.println("[WHERE IN 子查询]");
                System.out.println("  左侧字段:" + SQLUtils.toSQLString(x.getExpr()));
                System.out.println("  子查询 SQL:" + SQLUtils.toSQLString(x.getSubQuery()));
                System.out.println();
                return true; // 继续遍历子节点(子查询内部)
            }

            @Override
            public boolean visit(SQLExistsExpr x) {
                System.out.println("[WHERE EXISTS 子查询]");
                System.out.println("  是否 NOT EXISTS:" + x.isNot());
                System.out.println("  子查询 SQL:" + SQLUtils.toSQLString(x.getSubQuery()));
                System.out.println();
                return true;
            }

            @Override
            public boolean visit(SQLAnyExpr x) {
                System.out.println("[WHERE ANY 子查询]");
                System.out.println("  子查询 SQL:" + SQLUtils.toSQLString(x.getSubQuery()));
                System.out.println();
                return true;
            }

            @Override
            public boolean visit(SQLAllExpr x) {
                System.out.println("[WHERE ALL 子查询]");
                System.out.println("  子查询 SQL:" + SQLUtils.toSQLString(x.getSubQuery()));
                System.out.println();
                return true;
            }

            @Override
            public boolean visit(SQLSubqueryTableSource x) {
                System.out.println("[FROM 派生表子查询]");
                System.out.println("  子查询别名:" + x.getAlias());
                System.out.println("  子查询 SQL:" + SQLUtils.toSQLString(x.getSelect()));
                System.out.println();
                return true;
            }

            @Override
            public boolean visit(SQLQueryExpr x) {
                System.out.println("[SELECT 标量子查询]");
                System.out.println("  子查询 SQL:" + SQLUtils.toSQLString(x.getSubQuery()));
                System.out.println();
                return true;
            }
        });
    }

    /**
     * 判断子查询类型(返回中文描述)
     */
    public static String getSubQueryType(SQLObject obj) {
        if (obj instanceof SQLInSubQueryExpr) {
            return "WHERE IN 子查询";
        } else if (obj instanceof SQLExistsExpr) {
            return "WHERE EXISTS 子查询";
        } else if (obj instanceof SQLAnyExpr) {
            return "WHERE ANY 子查询";
        } else if (obj instanceof SQLAllExpr) {
            return "WHERE ALL 子查询";
        } else if (obj instanceof SQLSubqueryTableSource) {
            return "FROM 派生表子查询";
        } else if (obj instanceof SQLQueryExpr) {
            return "SELECT 标量子查询";
        } else {
            return "非子查询对象";
        }
    }

    // 测试
    public static void main(String[] args) {
        String sql = "SELECT " +
                "id, " +
                "(SELECT name FROM user WHERE user.id = order.user_id) AS user_name " +
                "FROM (SELECT id, user_id FROM `order` WHERE status = 1) AS t " +
                "WHERE id IN (SELECT order_id FROM pay WHERE amount > 100) " +
                "AND EXISTS (SELECT 1 FROM log WHERE log.order_id = t.id) " +
                "AND amount > ANY (SELECT amount FROM pay WHERE status = 0)";
        
        parseAllSubQueries(sql);
    }
}

五、避坑指南(必看,避免解析失败)

  1. 必须指定数据库方言:解析时一定要传入 JdbcConstants.MYSQL / ORACLE 等,否则子查询解析会出现异常(比如无法识别 MySQL 特有的语法)。

  2. 区分 SQLQueryExpr 和 SQLSubqueryTableSource:前者是「标量子查询(表达式)」,后者是「派生表(表来源)」,不要混淆两者的父类。

  3. 比较运算符子查询的解析逻辑:先判断是否为 SQLBinaryOpExpr,再判断右侧是否为 SQLQueryExpr,不能直接判断为 SQLQueryExpr(因为它是嵌套在二元运算中的)。

  4. 子查询别名:FROM 后的子查询必须有别名,否则解析时不会报错,但执行 SQL 时会语法错误,工具类中可添加别名校验。

  5. 多层子查询:用 Visitor 模式可递归解析多层子查询(比如子查询中又包含子查询),无需手动递归。

六、总结(最精简,方便记忆)

6.1 核心子查询类(真实存在,无虚构)

  • WHERE 子查询:SQLInSubQueryExpr(IN)、SQLExistsExpr(EXISTS)、SQLAnyExpr(ANY)、SQLAllExpr(ALL)、SQLBinaryOpExpr+SQLQueryExpr(比较运算符)

  • FROM 子查询:SQLSubqueryTableSource(派生表)

  • SELECT 子查询:SQLQueryExpr(标量子查询)

6.2 核心逻辑

子查询的解析本质是「识别子查询的位置 + 对应 Druid 类」,所有子查询最终都会被解析成对应的 Java 对象,通过这些对象可以提取子查询的 SQL、条件、表等信息,也可以进行改写(比如添加多租户条件、过滤条件)。

6.3 常用场景

企业开发中,子查询解析常用于「SQL 审计(提取子查询关联表)、SQL 改写(给子查询加条件)、数据权限控制(拦截非法子查询)」等场景,本文提供的工具类可直接适配这些场景。

posted @ 2026-05-08 16:13  蓝迷梦  阅读(14)  评论(0)    收藏  举报