py提取sql表名

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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值