用SQL从SQL查询语句中提取表名信息


=Start=

缘由:

简单整理一下前段时间做的用Hive SQL从SQL查询语句中提取表名的内容,方便后面有需要的时候参考(之前记录过用Python和Java从SQL中提取表名、列名的功能,但这里出于方便起见——不想写UDF,准备直接在ETL里用Hive SQL来实现)。

正文:

参考解答:
简单快速的一个实现
-- 从mysql查询语句中提取表名

,substring_index(split(lower(query_sql),'from\\s{1,}|where')[1],' ',1) as tbl_name

,split(split(lower(query_sql),'from\\s{1,}|where')[1],'\\s{1,}')[0] as tbl_name

-- 已知问题:
-- 对于有 join 等多表连接的无法将所有表名都拿到;
-- 对于整体语句中存在注释语句的无法很好识别(除非是所有注释语句都放到了最后);
完整的提取表名的思路
/*
从完整的SQL语句字符串中提取表名的思路和步骤

1. 先用正则表达式将SQL查询中的注释行替换为空,生成真正有效的SQL语句,为后面的表名提取排除干扰
2. 对没有多表连接的查询进行提取(可以保证提取的准确率);
3. 再对当前已知的几种表连接方式尝试提取,可以将提取出来的结果放在多行中,方便后面的关联匹配;
4. 有数据库名的也用一个字段提取出来备着;
5. 增加几个字段用来给SQL打标签(所有列/where条件/limit数量);
*/

/*
根据抽样的情况来看,SQL语句中:
from 之后的第一个字段是 table1
join 之后的第一个字段是 table2/3/...
正常情况下只用考虑 2 个表关联的情况,因为 3 个或以上表关联的毕竟是少数,不过可以统计一下比率,方便最后做决策
*/


-- todo: add some tag of sql, such as has */where/limit/--
-- 有几种方案在一列里面表示多个信息,比如:
-- 数字 1111111 每一位用1/0来表示这个位置上的目标字符串是否存在,判断起来是用取模等方式,但还没有完全想好
-- 字符串 abcdef 每一个不同的字符表示希望查找的目标字符串是否存在,判断起来也很方便,直接 locate/findstr/like 就行
分析过程中的一些代码片段
-- select.+?\n
-- 尝试用换行符进行切分,然后对每一个结果进行判断,不过这个只对部分SQL才能生效,和具体的SQL编写习惯有关(生产环境中各种可能性都有可能出现,只要它符合基本的规范原理就行,所以根本上还是从语法解析上着手是最靠谱的)
lateral view explode(split(query_sql,'\\n{1,}')) pi as sql1

-- 如何判断一个SQL语句中是否包含表连接的操作
-- 当前想到的3个判断条件是 join/on/from之后where之前有没有逗号
and (query_sql rlike '\\s{1,}join\\s{1,}' or query_sql rlike '\\s{1,}on\\s{1,}' or (query_sql like '%where%' and split(lower(query_sql),'from\\s{1,}|where\\s{1,}')[1] like '%,%'))


-- 切换处理思路,先将SQL语句做格式化处理,然后再通过split等方式进行切分提取,比如先将连续的多个空白符替换成空格
regexp_replace(query_sql,'\\s{1,}',' ')


-- 先用正则表达式将SQL查询中的注释行替换为空,生成真正有效的SQL语句,效果很好
,regexp_replace(query_sql,'--.+?\\n{1,}','') as real_sql
一个完整的从SQL提取表名的样例
-- 下面的SQL通过多层子查询,前期通过剔除注释语句和统一空白符进行规范化处理,然后通过对 from|join 等和表在一起的关键字进行 split 切分
-- 从而得到多个以表开头的字符串,借助 explode 将一行转成多行,然后再提取表名,并用 where 条件对无效切分数据进行排除
-- 最终得到的数据格式为SQL中使用到的每一个表为一行数据,方便后面做关联分析
SELECT
`user_name`
,`time_str`
,join_tag
,join_tag2
,join_cnt
,tbl_x
,tbl_namex
,real_sql
,`query_sql`
,`request_body`
FROM
(SELECT
`user_name`
,`time_str`
,case
when real_sql like '% join %' then 1
when real_sql like '% on %' then 2
else 0
end as join_tag
,if(split(real_sql,'from ')[1] like '%,%',1,0) as join_tag2
,(length(real_sql) - length(replace(real_sql,' join ',''))) / length(' join ') as join_cnt
,tbl_x
,substring_index(tbl_x,' ',1) as tbl_namex
,real_sql
,`query_sql`
,`request_body`
FROM
(SELECT
-- `dt`,
`user_name`
,`time_str`
,lower(regexp_replace(regexp_replace(query_sql,'--.+?\\n{1,}',''),'\\s{1,}',' ')) as real_sql
,`query_sql`
,`request_body`
-- todo: add some tag of sql, such as has */where/limit/--
-- ,if(query_sql like '%--%',1,0) as tag_comment
FROM
log.mysql_search_op
WHERE
dt between '20230315' and '20230315'
and query_sql like '%--%'
)x1
lateral view explode(split(real_sql,' from | join ')) pi as tbl_x
)x2
where join_tag > 0 and tbl_namex not in ('','select')
LIMIT 1000
;
参考链接:

如何用Python从SQL中提取出涉及到的表名、列名?
https://ixyzero.com/blog/archives/4404.html

How to extract tables names in a SQL script?
https://stackoverflow.com/questions/49773059/how-to-extract-tables-names-in-a-sql-script

如何用Java从SQL中提取出涉及到的表名、列名?
https://ixyzero.com/blog/archives/4405.html

MySQL多表join连接查询
https://ixyzero.com/blog/archives/4211.html

Hive SQL学习整理
https://ixyzero.com/blog/archives/4245.html

SQL 查询并不是从 SELECT 开始的(SQL queries don’t start with SELECT)
https://mp.weixin.qq.com/s/5BF12rIs1QXQpX4vpKkL0Q

=END=


《 “用SQL从SQL查询语句中提取表名信息” 》 有 2 条评论

  1. Using joins to combine data from different tables in MySQL
    https://www.prisma.io/dataguide/mysql/reading-and-querying-data/joining-tables

    How to Join Two Tables in MySQL
    https://www.cloudways.com/blog/how-to-join-two-tables-mysql/
    “`
    常见的mysql多表关联查询语法格式整理

    # 有 join/on 关键字,且形式固定(from和join之后的就是表名),比较好提取
    select *
    from table1
    join table2
    on table1.column = table2.column;

    # 没有 join/on 关键字,通过 where 条件进行关联,需要截取from和where关键字之间的内容并进行切分后提取,但是存在误报可能,需要提前进行过滤规避
    select A.id,A.name,B.name
    from table1 A, table2 B
    where A.id = B.id;

    ## 容易引起误报的case
    select *
    from table1
    group by col1,col2
    where condition;

    # 还有子查询的几种case也是需要考虑的
    # case1
    select *
    from article
    where uid in (select uid from user where status=1);

    # case2
    select *
    from
    (select column_name(s)
    from table1
    where condition
    )

    # 简单来说,就是类似于SQL语句解析/信息提取相关的工作,用正则是一种短平快的方案,用于快速创建demo验证思路流程是否可行是很好的,但对于后期需要考虑准召率的项目来说,这类功能用UDF(引入专业的类库来解析)来实现会更可靠。
    “`

    JSQLParser的简单使用
    https://ixyzero.com/blog/archives/4806.html

  2. /*
    1. 先用正则去掉注释的内容,并将SQL全转换成小写
    2. 再对 set 语句的内容进行移除;
    3. (可选)再对连续的空白符号进行处理;
    */
    regexp_replace(regexp_replace(lower(regexp_replace(query_sql,’–.+?\\n{1,}’,”)), ‘set .+?\073′, ”),’\\s{1,}’,’ ‘) as query_sql2,

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注