druid解析select语句----提取表名别名

druid1.2.24解析复杂SelectSQL语句——表名以及别名解析

用于SelectSQL后续的查询优化

在后端业务中涉及到复杂SQL中的Select表名以及别名解析是一个很重要的一个步骤。我们往往要从unionjoin的语句中提取到自己想要的信息。

关于SQL解析,这里就必须要提到阿里开源的druid。本文将会使用druid相关的SQL解析方法对一个简单的SQL进行解析,同时也会考虑到复杂SQL

这里我们从第一步开始。首先我们会拿到一个SQL。比如

String sql = "select id from user a where uid = "123" limit 1";

首先我们需要构造一个用于存储每一个表信息的mapList。

List<Map<String,String>> aliasToTableMapList = Lists.newArrayList();

然后我们开始对druid解析SQL的一些方法进行了解。

首先是SQLStatementParser,我们首先需要通过druid提供的SQL解析类SQLParserUtils创建一个SQL语句解析类。具体方法如下。

SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, "mysql");

这里需要将我们的SQL传入这个创建SQL解析类中,这里可以指定类型,这里可以使用druid官方提供的DbType枚举类进行选择,感兴趣的可以自己去进入源码进行了解。也可以自己直接使用字符串进行指定,比如我使用方式。

接下来就是将我们需要解析的SQL语句中的解析语句列表提取出来。

List<SQLStatement> statementList = parser.parseStatementList();

这里由于我们只有一个简单SelectSQL。所以这里解析出来的列表的长度只有1。

接下来就需要我们对获取到的这个statementList中的statement进行递归遍历获取里面的信息。

for (SQLStatement statement : statementList) {
    if (statement instanceof SQLSelectStatement) {
        SQLSelect select = ((SQLSelectStatement) statement).getSelect();
        SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock) select.getQuery();
        processTableSource(queryBlock.getFrom(), aliasToTableMapList);
    }
}

因为本文解析SQL是使用的select语句,这里我们需要确保是Select语句,所以我们需要对这个语句进行instanceof判断。然后需要我们将这个语句中的select取出,然后拿到选择块。具体的类中的一些属性感兴趣的可以自行了解,这里不过多深入。然后我们开始进行递归操作,遍历语句中的每一条语句。

private static void processTableSource(SQLTableSource tableSource, List<Map<String, String>> aliasToTableMapList) {
    if (tableSource instanceof com.alibaba.druid.sql.ast.statement.SQLExprTableSource) {
        com.alibaba.druid.sql.ast.statement.SQLExprTableSource exprTableSource = (com.alibaba.druid.sql.ast.statement.SQLExprTableSource) tableSource;
        String alias = exprTableSource.getAlias();
        if (alias != null) {
            if (exprTableSource.getExpr() instanceof SQLIdentifierExpr) {
                SQLIdentifierExpr identifierExpr = (SQLIdentifierExpr) exprTableSource.getExpr();
                String tableName = identifierExpr.getName();
                Map<String,String> aliasMap = Maps.newHashMap();
                aliasMap.put("alias", alias);
                aliasMap.put("tableName", tableName)
                aliasToTableMapList.add(aliasMap);
            }else if (exprTableSource.getExpr() instanceof SQLPropertyExpr) {
                SQLPropertyExpr propertyExpr = (SQLPropertyExpr) exprTableSource.getExpr();
                if (StringUtils.isNotEmpty(propertyExpr.getOwnerName())) {
                    Map<String,String> aliasMap = Maps.newHashMap();
                    aliasMap.put("alias", alias);
                    aliasMap.put("tableName",propertyExpr.getName());
                    aliasToTableMapList.add(aliasMap);
                }
            }
        }else{
            SQLPropertyExpr propertyExpr = (SQLPropertyExpr) exprTableSource.getExpr();
            if(StringUtils.isNotEmpty(propertyExpr.getOwnerName())){
                Map<String,String> aliasMap = Maps.newHashMap();
                aliasMap.put("tableName",propertyExpr.getName());
                aliasToTableMapList.add(aliasMap);
            }

        }
    }

在druid解析SQL的过程中,最后都会走到SQLExprTableSource。这是最后SQL执行的语句。因为你可能存在子查询等操作,所以需要遍历到最底层才可以去提取到表的一些信息,别名,表名等。上面添加别名的操作就不详细解释了,主要就是分为两个部分,一个是没有别名的时候的操作,一个是存在别名的时候的操作(这里面也需要区分两种情况)。

然后就是最关键的地方,就是如何更进一步的递归遍历。

else if (tableSource instanceof com.alibaba.druid.sql.ast.statement.SQLJoinTableSource) {
    com.alibaba.druid.sql.ast.statement.SQLJoinTableSource joinTableSource = (com.alibaba.druid.sql.ast.statement.SQLJoinTableSource) tableSource;
    processTableSource(joinTableSource.getLeft(), aliasToTableMapList);
    processTableSource(joinTableSource.getRight(), aliasToTableMapList);
}else if(tableSource instanceof com.alibaba.druid.sql.ast.statement.SQLUnionQueryTableSource){
    com.alibaba.druid.sql.ast.statement.SQLUnionQueryTableSource unionQueryTableSource = (com.alibaba.druid.sql.ast.statement.SQLUnionQueryTableSource) tableSource;
    SQLUnionQuery sqlUnionQuery = unionQueryTableSource.getUnion();
    List<SQLSelectQueryBlock> relations = sqlUnionQuery.getRelations().stream().map(item -> (SQLSelectQueryBlock) item).collect(Collectors.toList());
    relations.forEach(queryBlock -> {processTableSource(queryBlock.getFrom(), aliasToTableMapList);});
}else if(tableSource instanceof com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock){
    com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock queryBlock = (com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock) tableSource;
    processTableSource(queryBlock.getFrom(), aliasToTableMapList);
}

在一个SQL中,不会所有的SQL都会像本文提出的例子一样简单,我们这里需要考虑到一个复杂SelectSQL语句中可能出现的情况。本文主要考虑到了是join、union以及查询嵌套出现子查询的情况。这里最为复杂的就是join以及union。join主要的难点在于对左右连接进行遍历。而union的难点在于它查询连接的数目,所以可以看到在上方主要使用了SQLUnionQueryTableSource中的一个属性值unionQueryTableSource.getUnion().getRelations()。它可以获取到每一个连接中使用到的查询块,然后递归的进行遍历,以获取这个查询块中还可能存在的子查询等。

最后会的到一个结果,存储各个表名。当然,具体的需求还需要具体分析。

本文就到此了,比较浅显,需要深入的还需要各位友友自己去了解了。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值