python提取sql中的所有表名
工作中遇到修改sql逻辑增减表后需要调整调度依赖的问题
其中对于大段sql或多个sql手动捞表名是很费事费力的事情;
所以想从网上找个脚本来干这个活,找了半天发现大概就两个一个用py的包sqlparse、sql_metadata 另一个就是正则处理文本;
个人测试这两个对于复杂一点的sql就处理不了,满足不了我的需求
例如:
处理不了这个看上去奇怪但是编译上没有问题,执行应该也没问题的sql
文件名:test_sql.sql
WITH TMP1 AS( --
SELECT A.*,1 FROM (
DWAPP.DWL_EBG_CONTRACT_INFO_TMP
) A----------
left join DWAPP.DWL_EBG_CONTRACT_SUM D2
on A.xx=D2.xx
where A.qq=1
),
TMP2 AS (
SELECT*, b.xx,b.qq,b.rr,*FROM DWAPP.DWL_EBG_CONTRACT_INFO_TMP1 B-- V这是你擦过
join SS.qwert_OOO V
on V.xx=B.xx join
dd.lkju_rrr as yy on V.ss=yy.ss/*
TMP1 C
JOIN
TMP2 D --JOIN 可能替换为LEFT JOIN , INNER JOIN , RIGHT JOIN
ON C.XX = D.XX(+)
full join--
AS.qwdcv_rtg QQ--
/*on QQ.ww = D.ww--
inner join
DS.qwdcv_rtg HH
on HH.cc = QQ.cc)
*/-----
where B.ss=V.ss
) select * from (SELECT C.HW_CONTRACT_NUM, D.CUST_NUM
FROM TMP1 C
JOIN
TMP2 D --JOIN 可能替换为LEFT JOIN , INNER JOIN , RIGHT JOIN
ON C.XX = D.XX(+)
full join--
(AS.qwdcv_rtg) QQ--
on QQ.ww = D.ww--
inner join
DS.qwdcv_rtg HH
on HH.cc = QQ.cc)
;
所以根据用网上正则和纯文本方式处理sql来提取表名的一个逻辑,
自己写了一个python,也是用文本方式根据sql书写规范来提取表名
代码如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# date: 2024/07/21
# filename: d_sql_table_2
# version: 1.2
import re
# 表前边的关键词
TABLE_QZ = ('FROM', 'JOIN', 'UPDATE', 'FROM(', 'JOIN(')
def get_query_sql(file_path):
'''读取文本中的sql'''
with open(file_path, 'r', encoding='utf-8') as f:
# 文本预处理:
# 1、打开文件,获取文本内容,转换成大写,去除前后空格,去除换行符
content = f.read().upper().strip(' ').split('\n')
content_list = []
for i in content:
if not i.startswith('--') and i != '':
# 2、去除单行注释
j = i if '--' not in i else i.replace(i, i.split('--')[0])
# 3、去除tab符
content_list.append(j.replace('\t', ' '))
# 4、双空格转为单空格
content = ' '.join(content_list).replace(' ', ' ').replace(' ', ' ')
# 5、去除多行注释
comments = re.findall('/\*.*?\*/', content)
for i in comments:
content = content.replace(i, '')
return content
def replace_spaces(text):
pattern=r'\s+'
replacement=' '
return re.sub(pattern,replacement,text)
def get_tables(query_sql):
sql_list = query_sql.split(' ')
#sql_list2=sql_list
sources_tables = []
ton = 0
for i in sql_list:
if i in TABLE_QZ:
ton += 1
if sql_list[ton] != 'SELECT':
sources_tables.append(sql_list[ton])
#sql_list2.pop(ton)
else:
ton += 1
# for j in sql_list:
# if j == 'JOIN':
# jon += 1
# sources_tables.append(sql_list[jon])
# else:
# jon += 1
sources_tables = [i.replace('(', '').replace(')', '') for i in sources_tables]
return sources_tables #, sql_list2
if __name__ == '__main__':
#读取sql文件
file_path = r'D:\JetBrains\pythonProject\test_sql.sql'
# 格式化sql代码
query_sql = get_query_sql(file_path)
#print(query_sql)
# 格式化 变成一行的sql里的多个空格变为1个
query_sql_1 = replace_spaces(query_sql)
#print(query_sql_1)
query_sql_2 = query_sql_1.replace('*', ' ').replace('(', '( ')
#print(query_sql_2)
query_sql_3 = replace_spaces(query_sql_2)
#print(query_sql_3)
# 格式化 只有1空格的sql中的from ( -> FROM( 再从中去除 FROM( 因为from(后边没有表 是子查询
query_sql_4 = query_sql_3.replace('FROM (', 'FROM(')
# 格式化 JOIN ( -> JOIN( 再从中去除 JOIN( 因为JOIN(后边没有表 是子查询
query_sql_5 = query_sql_4.replace('JOIN (', 'JOIN(')
print(query_sql_5)
# 格式化,-> , 保证逗号前后必有空格
#query_sql_4 = query_sql_3.replace(',', ' , ')
# 格式化 多个空格变为1个
#query_sql_5 = replace_spaces(query_sql_4)
# 存在bug 如果 from或者join后边带括号 且括号里只有一个表名 则现有逻辑无法处理 会取不出来 例子:from(DWAPP.DWL_EBG_CONTRACT_INFO_TMP) 这样写sql不会报错 V1.2已解决
# 新增问题如果表名叫SELECT 取不到
# 存在bug 无法处理 from 后面有多个表名 用,分隔 where 筛选条件 oracle的特殊写法无法处理 例子:from ESW.DWL_EBG_CONTRACT a,ESW.DWL_EBG_SUM b where a.xx=b.xx
#l1, l2 = get_tables(query_sql_5)
l1 = get_tables(query_sql_5)
#print(l1)
#print(l2)
print('\n')
for i in l1:
print(i)
经测试基本可是处理绝大多数sql,但是无法处理oracle的一种写法
例如:
SELECT A.*,1 FROM
DWAPP.DWL_EBG_CONTRACT_INFO_TMP A,
DWAPP.DWL_EBG_CONTRACT_SUM D2
where A.xx=D2.xx
and A.qq=1
只能拿到 from 后的一个表
DWAPP.DWL_EBG_CONTRACT_INFO_TMP
其他的表取不到
优点是只用了re一个包,逻辑也不复杂但很有效,能满足我的需求
对于无法处理的写法暂时的解决方法是使用前先看一遍sql手动把有这种情况的处理一下
例如把上面sql处理成这样:
SELECT A.*,1 FROM
DWAPP.DWL_EBG_CONTRACT_INFO_TMP A join
DWAPP.DWL_EBG_CONTRACT_SUM D2
where A.xx=D2.xx
and A.qq=1
,替换成 join (只替换from 到 where 之间的,就可以 join前后必须各有一个空格)
简单update和delete可以处理;update两表关联更新和merge无法处理
以后有逻辑能处理再更新,有什么好的办法也请大佬提示一下
V1.3
此脚本为了取出sql逻辑里需要用到数据的表
UPDATE后边跟的是被更新的表所以不取;
DELETE from 后边跟着的表是被删除的表不取;
MERGE into 后边的被更新的表所以不取;
明确以上条件,又冥思苦想了针对特殊写法from后多个表名
如上面提到的例子,和有可能出现的奇怪写法如
SELECT A.*,1 FROM
(DWAPP.DWL_EBG_CONTRACT_INFO_TMP)as A
,( DWAPP.DWL_EBG_CONTRACT_SUM )D2
where A.xx=D2.xx
and A.qq=1
的处理逻辑
新代码如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# date: 2024/07/23
# filename: d_sql_table_2
# version: 1.4
# author: zs
import re
# 表前边的关键词
# V1.3 去掉UPDATE 此脚本为了取出sql逻辑里需要用到数据的表 UPDATE后边跟的是被更新的表所以不取
# TABLE_QZ = ('FROM', 'JOIN', 'UPDATE', 'FROM(', 'JOIN(')
TABLE_QZ_F = ('FROM', 'FROM(')
TABLE_QZ_J = ('JOIN', 'JOIN(')
def get_query_sql(file_path):
'''读取文本中的sql'''
with open(file_path, 'r', encoding='utf-8') as f:
# 文本预处理:
# 1、打开文件,获取文本内容,转换成大写,去除前后空格,去除换行符
content = f.read().upper().strip(' ') #.split('\n')
# 2、先去除多行注释
comments = re.findall('/\*.*?\*/', content)
for i in comments:
content = content.replace(i, '')
#再分隔
content = content.split('\n')
content_list = []
for i in content:
if not i.startswith('--') and i != '':
# 3、去除单行注释
j = i if '--' not in i else i.replace(i, i.split('--')[0])
# 4、去除tab符
content_list.append(j.replace('\t', ' '))
# 5、双空格转为单空格
content = ' '.join(content_list).replace(' ', ' ').replace(' ', ' ')
return content
def replace_spaces(text):
pattern=r'\s+'
replacement=' '
return re.sub(pattern,replacement,text)
def get_tables(query_sql):
sql_list = query_sql.split(' ')
#sql_list2=sql_list
sources_tables = []
ton = 0
for i in sql_list:
# from 和 from( 拿出来单独处理 select * from t;delete from t; select * from( t ) as a , ( t... where
# 0 1 2 3 4 5 6 7 8 9
# 0 1 2 3 4 5 6
if i in TABLE_QZ_F:
ton += 1
if ton < 2 and sql_list[ton] != 'SELECT':
sources_tables.append(sql_list[ton])
# 只取要用到数据的表 如果是DELETE from 后边跟着的表是被删除的表 不取
elif ton >= 2 and sql_list[ton] != 'SELECT' and sql_list[ton - 2] != 'DELETE':
# from 或 from( 后边不是 select 就是 表 先取出来
sources_tables.append(sql_list[ton])
# 定义布尔变量
don = False
# 截取list[ton from后边表的下标:ton+6 综合多种奇怪但不报错的写法 如果后边有逗号 那第一个逗号最远在从ton后边开始数的第5位] from 后边可能是 group by order by 后面跟多个字段也是用逗号分隔 所以增加判断
if 'BY' not in sql_list[ton:ton + 6]:
for j in sql_list[ton:ton + 6]:
if j == ',':
don = True
# True 代表 from 后边有多个表以,分隔 且后面一定有where
if don:
con = 0
# 截取list遍历 ,后边只有 ( 或者 表
for h in sql_list[ton:]:
con += 1
# ,后边不是( 一定是表
if h == ',' and sql_list[ton + con ] != '(':
sources_tables.append(sql_list[ton + con ])
# ,后边是( (后边一定是表
elif h == ',' and sql_list[ton + con ] == '(':
sources_tables.append(sql_list[ton + con + 1])
# 遍历到第一个遇到的where证明这个含有多个表以逗号分隔的sql逻辑结束
elif h == 'WHERE':
break
# join 和 join(
elif i in TABLE_QZ_J:
ton += 1
if sql_list[ton] != 'SELECT':
sources_tables.append(sql_list[ton])
else:
ton += 1
# for j in sql_list:
# if j == 'JOIN':
# jon += 1
# sources_tables.append(sql_list[jon])
# else:
# jon += 1
sources_tables = [i.replace('(', '').replace(')', '') for i in sources_tables]
return sources_tables #, sql_list2
def remove_duplicates(lst):
# 列表去重
return [x for i, x in enumerate(lst) if x not in lst[:i]]
if __name__ == '__main__':
#读取sql文件
file_path = r'D:\JetBrains\pythonProject\test_sql.sql'
# 格式化sql代码
query_sql = get_query_sql(file_path)
#print(query_sql)
# 格式化 变成一行的sql里的多个空格变为1个
query_sql_1 = replace_spaces(query_sql)
#print(query_sql_1)
query_sql_2 = query_sql_1.replace('*', ' ').replace('(', '( ').replace(')', ') ').replace(',', ' , ')
#print(query_sql_2)
query_sql_3 = replace_spaces(query_sql_2)
#print(query_sql_3)
# 格式化 只有1空格的sql中的from ( -> FROM( 再从中去除 FROM( 因为from(后边没有表 是子查询
query_sql_4 = query_sql_3.replace('FROM (', 'FROM(')
# 格式化 JOIN ( -> JOIN( 再从中去除 JOIN( 因为JOIN(后边没有表 是子查询
query_sql_5 = query_sql_4.replace('JOIN (', 'JOIN(')
print(query_sql_5)
# 格式化,-> , 保证逗号前后必有空格
#query_sql_4 = query_sql_3.replace(',', ' , ')
# 格式化 多个空格变为1个
#query_sql_5 = replace_spaces(query_sql_4)
# 存在bug 如果 from或者join后边带括号 且括号里只有一个表名 则现有逻辑无法处理 会取不出来 例子:from(DWAPP.DWL_EBG_CONTRACT_INFO_TMP) 这样写sql不会报错 V1.2已解决
# 新增问题如果表名叫SELECT 取不到
# 存在bug 无法处理 from 后面有多个表名 用,分隔 where 筛选条件 oracle的特殊写法无法处理 例子:from ESW.DWL_EBG_CONTRACT a,ESW.DWL_EBG_SUM b where a.xx=b.xx V1.3已解决
# V1.3 from 后面有多个表名 用,分隔 where 筛选条件 (有看了下好像并不只有oracle能这样写) 增加多段循环和判断 测试了多种情况 多个表名可以取出 (不确定还有没有其他情况没想到)
#l1, l2 = get_tables(query_sql_5)
l1 = get_tables(query_sql_5)
# list 元素去重
l2 = remove_duplicates(l1)
print(l1)
print(l2)
for i in l2:
print(i)
测试后解决了之前的bug,只是如果有表名真叫SELECT那暂时没辙
如果有什么其他bug请留言告诉我,感谢
V1.5
更新了读取sql去注释的方法
存在
-- 后面可能有/* 这样/*不起作用 --起作用
-- 后面可能有*/ 这样的*/起不起作用取决于前面的最近的/*前无--的/*
所以增加判断
def get_query_sql(file_path):
'''读取文本中的sql'''
with open(file_path, 'r', encoding='utf-8') as f:
# 文本预处理:
# 1、打开文件,获取文本内容,转换成大写,去除前后空格,去除换行符
content = f.read().upper().strip(' ').split('\n')
content_list = []
for i in content:
if not i.startswith('--') and i != '':
# 2、去除单行注释
if '--' not in i:
content_list.append(i.replace('\t', ' '))
elif '--' in i and '*/' in i:
content_list.append(i.replace('\t', ' '))
elif '--' in i and '*/' not in i:
j = i.split('--')[0]
content_list.append(j.replace('\t', ' '))
elif i.startswith('--') and i != '' and '*/' in i:
content_list.append(i.replace('\t', ' '))
# 4、双空格转为单空格
content = ' '.join(content_list).replace(' ', ' ').replace(' ', ' ')
# 5、去除多行注释
comments = re.findall('/\*.*?\*/', content)
print(comments)
for i in comments:
content = content.replace(i, '')
return content
整体代码为
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# date: 2024/08/01
# filename: d_sql_table_2
# version: 1.5
# author: zs
import re
# 表前边的关键词
# V1.3 去掉UPDATE 此脚本为了取出sql逻辑里需要用到数据的表 UPDATE后边跟的是被更新的表所以不取
# TABLE_QZ = ('FROM', 'JOIN', 'UPDATE', 'FROM(', 'JOIN(')
TABLE_QZ_F = ('FROM', 'FROM(')
TABLE_QZ_J = ('JOIN', 'JOIN(')
def get_query_sql(file_path):
'''读取文本中的sql'''
with open(file_path, 'r', encoding='utf-8') as f:
# 文本预处理:
# 1、打开文件,获取文本内容,转换成大写,去除前后空格,去除换行符
content = f.read().upper().strip(' ').split('\n')
content_list = []
for i in content:
if not i.startswith('--') and i != '':
# 2、去除单行注释
if '--' not in i:
content_list.append(i.replace('\t', ' '))
elif '--' in i and '*/' in i:
content_list.append(i.replace('\t', ' '))
elif '--' in i and '*/' not in i:
j = i.split('--')[0]
content_list.append(j.replace('\t', ' '))
elif i.startswith('--') and i != '' and '*/' in i:
content_list.append(i.replace('\t', ' '))
# 4、双空格转为单空格
content = ' '.join(content_list).replace(' ', ' ').replace(' ', ' ')
# 5、去除多行注释
comments = re.findall('/\*.*?\*/', content)
print(comments)
for i in comments:
content = content.replace(i, '')
return content
def replace_spaces(text):
pattern = r'\s+'
replacement = ' '
return re.sub(pattern, replacement, text)
def get_tables(query_sql):
sql_list = query_sql.split(' ')
# sql_list2=sql_list
sources_tables = []
ton = 0
for i in sql_list:
# from 和 from( 拿出来单独处理 select * from t;delete from t; select * from( t ) as a , ( t... where
# 0 1 2 3 4 5 6 7 8 9
# 0 1 2 3 4 5 6
if i in TABLE_QZ_F:
ton += 1
if ton < 2 and sql_list[ton] != 'SELECT':
sources_tables.append(sql_list[ton])
# 只取要用到数据的表 如果是DELETE from 后边跟着的表是被删除的表 不取
elif ton >= 2 and sql_list[ton] != 'SELECT' and sql_list[ton - 2] != 'DELETE':
# from 或 from( 后边不是 select 就是 表 先取出来
sources_tables.append(sql_list[ton])
# 定义布尔变量
don = False
# 截取list[ton from后边表的下标:ton+6 综合多种奇怪但不报错的写法 如果后边有逗号 那第一个逗号最远在从ton后边开始数的第5位] from 后边可能是 group by order by 后面跟多个字段也是用逗号分隔 所以增加判断
if 'BY' in sql_list[ton:ton + 6]:
for j in sql_list[ton:ton + 6]:
if j == ',':
don = True
# True 代表 from 后边有多个表以,分隔 且后面一定有where
if don:
con = 0
# 截取list遍历 ,后边只有 ( 或者 表
for h in sql_list[ton:]:
con += 1
# ,后边不是( 一定是表
if h == ',' and sql_list[ton + con] != '(':
sources_tables.append(sql_list[ton + con])
# ,后边是( (后边一定是表
elif h == ',' and sql_list[ton + con] == '(':
sources_tables.append(sql_list[ton + con + 1])
# 遍历到第一个遇到的where证明这个含有多个表以逗号分隔的sql逻辑结束
elif h == 'WHERE':
break
# join 和 join(
elif i in TABLE_QZ_J:
ton += 1
if sql_list[ton] != 'SELECT':
sources_tables.append(sql_list[ton])
else:
ton += 1
# for j in sql_list:
# if j == 'JOIN':
# jon += 1
# sources_tables.append(sql_list[jon])
# else:
# jon += 1
sources_tables = [i.replace('(', '').replace(')', '') for i in sources_tables]
return sources_tables # , sql_list2
def remove_duplicates(lst):
# 列表去重
return [x for i, x in enumerate(lst) if x not in lst[:i]]
if __name__ == '__main__':
# 读取sql文件
file_path = r'D:\runjian\JetBrains\pythonProject\test_sql.sql'
# 格式化sql代码
query_sql = get_query_sql(file_path)
# print(query_sql)
# 格式化 变成一行的sql里的多个空格变为1个
query_sql_1 = replace_spaces(query_sql)
# print(query_sql_1)
query_sql_2 = query_sql_1.replace('*', ' ').replace('(', ' ( ').replace(')', ' ) ').replace(',', ' , ')
# print(query_sql_2)
query_sql_3 = replace_spaces(query_sql_2)
# print(query_sql_3)
# 格式化 只有1空格的sql中的from ( -> FROM( 再从中去除 FROM( 因为from(后边没有表 是子查询
query_sql_4 = query_sql_3.replace('FROM (', 'FROM(')
# 格式化 JOIN ( -> JOIN( 再从中去除 JOIN( 因为JOIN(后边没有表 是子查询
query_sql_5 = query_sql_4.replace('JOIN (', 'JOIN(')
print(query_sql_5)
# 格式化,-> , 保证逗号前后必有空格
# query_sql_4 = query_sql_3.replace(',', ' , ')
# 格式化 多个空格变为1个
# query_sql_5 = replace_spaces(query_sql_4)
# 存在bug 如果 from或者join后边带括号 且括号里只有一个表名 则现有逻辑无法处理 会取不出来 例子:from(DWAPP.DWL_EBG_CONTRACT_INFO_TMP) 这样写sql不会报错 V1.2已解决
# 新增问题如果表名叫SELECT 取不到
# 存在bug 无法处理 from 后面有多个表名 用,分隔 where 筛选条件 oracle的特殊写法无法处理 例子:from ESW.DWL_EBG_CONTRACT a,ESW.DWL_EBG_SUM b where a.xx=b.xx V1.3已解决
# V1.3 from 后面有多个表名 用,分隔 where 筛选条件 (有看了下好像并不只有oracle能这样写) 增加多段循环和判断 测试了多种情况 多个表名可以取出 (不确定还有没有其他情况没想到)
#V1.4 增加了对group by order by 后面存在多个字段有逗号会被V1.3的逻辑误判的判断
#V1.5 优化了对sql注释的去除规则
# l1, l2 = get_tables(query_sql_5)
l1 = get_tables(query_sql_5)
# list 元素去重
l2 = remove_duplicates(l1)
print(l1)
print(l2)
for i in l2:
print(i)