前言

先上成果预览图吧

作为一个数据库sql开发者,肯定有很多人和我一样,想要有一个工具,能传入任意sql,解析出sql中的所有表。
我之前有一篇文章【AIO】将任意查询sql转换成带远程数据库DBLINK的sql 中就提到了,使用纯文本硬解析会存在很多不确定因素,比如oracle新版本就添加了新的sql语法,有些场景太难处理,而解析器则只需要配置好规则,并且标准化规则的语法,那么扩展性就很强了。

antlr4 https://github.com/antlr/antlr4

Antlr这个老早就有了,如今已经比较成熟,像代码高亮插件prism就是用的这个。
但是,我找了好久,竟然在网上没找到一份有关“识别sql中的表名”的现成完整代码或者程序!解析其他语言的倒是有,但最后解析出来一个树就啥也不提了。
另外也找了很多问答,都是说Antlr4再加grammars-v4就可以解析sql了,但是就是不给代码。
无奈之下,我只能抱着github啃作者的文档,这作者只给了个使用Antlr4解析器最简单的例子,没给个实际开发语言解析的例子,我就只能靠着自己的理解加上看源码来看到底是怎么回事了。

首先,翻了很多文章,了解了Antlr4本身是个java程序,定义为解析器生成程序,而不是解析器本身。
可以编写符合规范的解析文档,文件名后缀为"*.g4",然后使用antlr4对这个g4文件生成其对应的解析器程序,在生成之前,可以指定好要生成哪种开发语言的能用的解析器,比如你想在python用,就传个python的参数进去,它就会生成对应的py文件;想生成java的,它就给你生成java文件。Antlr4支持的目标开发语言(不是指要解析的代码)目前多达9种

Cpp
CSharp
Dart
Go
Java
JavaScript
Python2
Python3
Swift

而理论上,Antlr4能解析的语法是无穷的,因为它本身并不带解析规则,解析规则完全依靠g4文件。

所以为了配合Antlr4,这个作者开了个项目,让大家都来写各种语言的g4文件
grammars-v4
https://github.com/antlr/grammars-v4
好家伙,打开这个项目一看,目前(2021-10-08)有多达240种语言的g4文件!

管他三七二十一,先把这两个项目的代码都下载下来。
然后对着Antlr4的文档,开干
https://github.com/antlr/antlr4/blob/master/doc/getting-started.md

操作步骤(windows)

第1步,安装java jdk

1.7版本以上,已安装请略过,前面说过了,Antlr4是java编写的解析器程序的生成程序,要运行java就得安装java,官方地址
https://www.oracle.com/java/technologies/downloads

第2步,下载Antlr4

地址 https://www.antlr.org/download/
找最新版的,文件名是这个格式的 antlr-4.9.2-complete.jar

建一个项目文件夹,我这里命名为 d:\sql_table,
把下载好的antlr-4.9.2-complete.jar文件,放入sql_table文件夹

第3步,设置环境变量

在系统环境变量中,添加 CLASSPATH 指向 d:\sql_table\antlr-4.9.2-complete.jar

第4步,下载并安装Antlr4的runtime

下载Antlr4的整个库
https://github.com/antlr/antlr4
打开压缩包,解压 \runtime\python3 文件夹至任意目录,
然后打开cmd,进入这个解压后的文件夹,
在cmd中运行

python setup.py install

第5步,获得plsql的语法文件

下载grammars-v4的整个库
https://github.com/antlr/grammars-v4
下载后,解压压缩包中以下几个文件,放到我们的项目目录d:\sql_table中去

  1. sql\plsql\PlSqlLexer.g4
  2. sql\plsql\PlSqlParser.g4
  3. sql\plsql\Python3\PlSqlLexerBase.py
  4. sql\plsql\Python3\PlSqlParserBase.py

第6步,生成解析器程序

打开cmd,定位到我们的项目目录 d:\sql_table
在cmd窗口中依次运行以下三条命令

doskey antlr4vpy3=java org.antlr.v4.Tool -Dlanguage=Python3 -no-listener -visitor $*antlr4vpy3 PlSqlLexer.g4antlr4vpy3 PlSqlParser.g4

运行后,目录内会生成以下几个文件

PlSqlLexer.py
PlSqlParserVisitor.py
PlSqlParser.py
PlSqlLexer.tokens
PlSqlLexer.interp
PlSqlParser.interp
PlSqlParser.tokens

至此,原材料才算准备齐全,接下来开始使用这个东西

第7步,测试

在刚刚解压的Antlr4\runtime\python3\bin目录下,有个pygrun文件,是用python语言编写的,把这个文件复制到我们的项目目录 d:\sql_table ,
然后打开cmd窗口,定位这里,在cmd’中执行,注意区分大小写

python pygrun -t PlSql sql_script

然后再输入一个sql,注意必须都大写, 比如

SELECT A FROM B

再 ctrl+z,回车
稍等一会儿,程序就会返回

有一说一,这个性能真的有点差。

这里提几点,

  1. pygrun 后面的参数中 -t表示生成树显示出来,不加就只解析语法是否正确。
  2. 必填的两个参数为 语法名称 和 开始规则
  3. 语法名称即为PlSqlLexer.py这个文件中, Lexer.py之前的几个字符,如果不匹配就会说找不到
  4. 传入SQL必须大写,作者说是希望规范化,但是实际上大小写混用真的很难识别

这个开始规则传什么,我研究了不少时间,它来源于PlSqlParser.g4文件中,一开始我想的是我要找from后面的表,就传了个from_clause,结果没识别出来,然后我又换成query_block,才识别出来,中间还被sql的大小写坑了,但是我换了个例子,用WITH开头的sql,又识别不出来了。仔细看这个PlSqlParser.g4文件的内容,是分一段一段的,其中有一段这个


subquery_basic_elements: query_block| '(' subquery ')';subquery_operation_part: (UNION ALL? | INTERSECT | MINUS) subquery_basic_elements;query_block: SELECT (DISTINCT | UNIQUE | ALL)? selected_listinto_clause? from_clause where_clause? hierarchical_query_clause? group_by_clause? model_clause? order_by_clause? fetch_clause?;

query_block作为一个开始规则,同时出现在了subquery_basic_elements下面,是不是代表这个是包含关系?
所以我一路找,找到了最顶层的,就是

sql_script: ((unit_statement | sql_plus_command) SEMICOLON?)* EOF;

把sql_script带进去,没错了,只是速度会比之前用query_block更慢,但这样兼容性更高

另外,这个pygrun有更新版本,支持界面显示树,加上 -g 参数即可,下载在这里
https://github.com/jszheng/py3antlr4book/tree/master/bin
效果如下

有了这个例子,我们就要看怎么把我们要的东西挑出来了

第8步,编写获取表名的程序

根据这个界面分析,拿着原始sql,一个个位置数,看哪些关键字用得上。
最后经过多种sql的验证,找出了Tableview_name这一层对应的start和stop中,包含有这个表名的开始位置和结束位置。
因为表名 可能由 “用户名.表名@DBLINK 别名” 这一长串组成,这个解析器会把这些词分成很多截,我需要的是个完整的,但不包含别名的部分,只有Tableview_name中的几个数字才能凑出准确的位置
即start中 第一个 逗号和第一个冒号之间的数字为开始位置,
stop中 ,第一个冒号和第一个等于号之间的数字加1为结束位置

分析到这里,
程序来了

import os
import re
from antlr4 import *
#file_name=r'test.sql'
tb_list=[]def get_table_position( node):if isinstance(node, ParserRuleContext):class_name = str(type(node))if str(class_name)=="<class 'PlSqlParser.PlSqlParser.Tableview_nameContext'>":tb_list.append([re.split(r'[,:]',str(node.start))[1],re.split(r'[:=]',str(node.stop))[1]])if hasattr(node, 'children'):for child in getattr(node, 'children'):get_table_position(child)return tb_listdef get_table(l_sql):lexerName = grammar + 'Lexer'parserName = grammar + 'Parser'module_lexer = __import__(lexerName, globals(), locals(), lexerName)class_lexer = getattr(module_lexer, lexerName)module_parser = __import__(parserName, globals(), locals(), parserName)class_parser = getattr(module_parser, parserName)#input_stream = FileStream(file_name)input_stream = InputStream(l_sql.upper())lexer = class_lexer(input_stream)token_stream = CommonTokenStream(lexer)token_stream.fill()parser = class_parser(token_stream)parser.buildParseTrees = Truefunc_start_rule = getattr(parser, start_rule)parser_ret = func_start_rule()tb_p=get_table_position(parser_ret)return tb_pgrammar='PlSql'
start_rule='sql_script'
l_sql='''select COLA C1 FROM U.TAB1
X,TAB2 WHERE X.COL2=4'''
tb_p=get_table(l_sql)
print('该SQL中含有的表如下')
for i in tb_p:print(str(l_sql)[int(i[0]):int(i[1])+1])

直接运行,就得到了本文最开始的那个效果。

有两行注释掉了,可以切换传入文件名

完整程序包

已完成新建文件夹,最近天气不好,有点累,休息一下,以后再弄

我打算把整个过程需要用到的文件,包括安装命令,封一个包出来,然后程序也封装成一个函数,输入文件或者字符串返回结果,另外加上那个自动转换成dblink的功能,再让这个程序兼容在oracle中使用,就完美了。

另外java版本的制作方式也极其类似,只多了一步,在生成解析器程序后,要用javac命令编译生成的解析器程序,我本地已测试成功,大佬们应该比我会玩,就不多说了

2021-10-15 更新

项目已上传github
https://github.com/Dark-Athena/list_table_sql-py

2021-10-16 更新

已支持在oracle数据库中调用
https://github.com/Dark-Athena/pl4py

  • 本文作者: DarkAthena
  • 本文链接: https://www.darkathena.top/archives/antlr4-sql-tablename
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!

【python】使用Antlr4实现识别sql中的表或视图名相关推荐

  1. 02-SQL语句给表起别名SQL中的表连接

    什么情况下需要给表起别名? 1.表名比较长 2.当需要在多个表中进行查询并把查询内容同时输出的时候 3.当需要进行表连接的时候(其实和2一个意思,一般情况下多个表进行连接主要目的就是为了从多个表中查询 ...

  2. 30-数据字典中的表或视围1

    数据库组件 数据字典中的表或视围 说 明 数据库 V$DATAFILE 记录系统的运行情况 表空间 DBA_TABLESPACES 记录系统表空间的基本信息 DBA_FREE_SPACE 记录系统表空 ...

  3. SQL中给表起别名的两个必须知道的知识点

    给列其别名: SELECT RTRIM(vend_name) + '(' + RTRIM(vend_contry) + ')' AS vend_title FROM Vendors ORDER BY ...

  4. SQL中,表之间的左联和右联是什么意思

    首先来看张图片: 定义: 左联:首先取出A表中所有数据,然后再加上A.B表通过关联字段key查询到的数据 右联:首先取出B表中所有数据,然后再加上A.B表通过关联字段key查询到的数据 内联:查询A. ...

  5. python识图 web_轻松简单搭建一个python的OCR服务器 识别截图中内容并提取

    Why? OCR(也称为光学字符识别)已经成为Python的常用工具.随着开源库Tesseract和Ocrad的出现,越来越多的程序员使用OCR编写自己程序.OCR的一个小例子,例如使用OCR直接从截 ...

  6. SQL中的表 与关系数据库

    SQL Server 中几个重要的表 1 主系统表 Sysobjexts 出现在每个数据库中,每个数据库对象含有一行记录 2 系统表 Syscolumns 出现在master数据库和侮个用户自定义的数 ...

  7. 交叉表的概念及sql中交叉表的使用

    交叉表是一种常用的分类汇总表格.使用交叉表查询,显示源于表中某个字段的汇总值,并将它们分组, 其中一组列在数据表的左侧,另一组列在数据表的上部. 行和列的交叉处可以对数据进行多种汇总计算,如:求和.平 ...

  8. mysql重复数据只取一条数据_My sql 中删除表中重复记录?只保留一条

    匿名用户 1级 2018-08-20 回答 在几千条记录里,存在着些相同的记录,如何能用SQL语句,删除掉重复的呢 1.查找表中多余的重复记录,重复记录是根据单个字段(peopleId)来判断 sel ...

  9. SQL中多表联查概念

    多表连接查询 当查询的字段来自多个表 分类:内连接:等值连接.非等值连接.自连接 外连接:左外连接.右外连接.全外连接 交叉连接 不同的 SQL JOIN 在展示具体语句结构之前,我们先列出您可以使用 ...

最新文章

  1. php 中间代码,PHP内核中用户函数、内部函数和中间代码的转换
  2. GIT的 .gitignore 配置
  3. 了解git的命令行使用
  4. android camera fragment,Android Camera 模块分析(三)
  5. creo管道设计教程_Creo7.0设计探索在管道设计的应用
  6. javascript 的线程问题
  7. Eclipse运行异常:Could not find the main class. Program will exit.
  8. Java 序列化漏洞多到修不完
  9. 认知维度与API的可用性评估
  10. php 上传微信媒体,关于微信公众号API多媒体上传
  11. ASP.NET 超市管理系统
  12. Unity 生成随机房间、洞穴(2D、3D地图)总结
  13. 81.【SpringMVC】
  14. vue elementui checkbox第一次点击选不中的问题
  15. windows注册表_改进Windows的20个最佳注册表技巧
  16. ubuntu top命令详解
  17. 计算机算法对程序设计的作用,【程序设计论文】高中算法与程序设计教学意义及有效性(共3796字)...
  18. mysql 的基本表的应用_Mysql的基本应用笔记1
  19. 脉冲函数、阶跃函数和斜坡函数
  20. CCF CSP 序列查询新解

热门文章

  1. 窥探Windows UUP 正向差分更新机制的高效性
  2. 畅想软件显示无法连接服务器,投屏神器怎么连接不上 连接电视方法详解
  3. 在谈声子谱计算的运动方程方法和动力学矩阵方法
  4. 怎么控制LED灯的开关呢?最简单的方法是给它加一个开关
  5. WebSocket(含表情包)-聊天室
  6. 两个世纪最近一次:一颗小行星近距离飞掠地球
  7. 暗光图像增强—opencv(C++)
  8. powerdesigner导出数据库word文件
  9. *现在感觉librealsense和realsense-ros的安装挺简单的(普通X86平台)(现在发现都有两种安装方式,下载源码编译或者二进制安装)
  10. 【Python-利用动态二维码传输文件(四)】使用pyautogui库录屏(连续截图),然后利用OpenCV逐张读取截图,识别当中的二维码信息,并把信息重组成原文件