数据血缘 | 探索SQLGlot的实用性与解析技巧
SQLGlot 可以让你对代码库中的 SQL 实现程序化理解。它能够创建测试和检查,以深入分析你的 SQL,并在源头识别错误或反模式。SQLGlot 提供了标准化不同数据仓库中 SQL 结构的 API,使它成为开发者的多用途工具。目前已经支持24种不同的sql语法了,就凭这个,也太值得用了。这是它的git地址,目前已经6.8K stars。
1.简介
SQLGlot 可以让你对代码库中的 SQL 实现程序化理解。它能够创建测试和检查,以深入分析你的 SQL,并在源头识别错误或反模式。SQLGlot 提供了标准化不同数据仓库中 SQL 结构的 API,使它成为开发者的多用途工具。
目前已经支持24种不同的sql语法了,就凭这个,也太值得用了。
这是它的git地址 tobymao/sqlglot: Python SQL Parser and Transpiler ,目前已经6.8K stars。
2.功能
stars那么多,功能上也十分强大。
-
格式化和翻译
-
元数据
-
解析器错误
-
不支持的错误
-
构建和修改 SQL
-
SQL 优化器
-
AST 自检
-
AST Diff
-
自定义方言
-
SQL 执行
最近在研究元数据做血缘,因此接下来结合一些案例来聊聊元数据相关内容,避免使用者踩坑。
3. 细说元数据
3.1 浅尝元数据解析
根据sql解析库、表信息 参考链接2
from sqlglot import parse_one, exp
query = """
SELECT
col1
,col2
,col3
FROM db1.table1
"""
for table in parse_one(query).find_all(exp.Table):
print(f"Table => {table.name} | DB => {table.db}")
执行上述python代码,很容易获取库表关系。当然 如果使用了别名,会被忽略,获取真正的库表名称。
根据sql解析字段
from sqlglot import parse_one, exp
query = """
SELECT
col1
,col2
,col3
FROM db1.table1
"""
for column in parse_one(query).find_all(exp.Column):
print(f"Column => {column.name}")
上述代码,能过获取所有字段级别的信息。
这些案例也可以在sqlglot的api文档中获取, sqlglot API documentation
不过我想吐槽一句,文档写的过于简单,且不容易理解。
3.2 复杂场景
以下案例只针对于表级别,实际应用中库表级别是有意义的。
场景一:复杂sql,多join
这种场景用上一张的案例依然是可以解决的,因此不展开讲
场景二:insert加复杂sql,多join
这种场景虽然可是使用第一个案例来解析,但是无法区分source表和target表。
# 获取target表
sql = ''' '''
target_tables = set()
for insert in parse_one(sql, read=db_type).find_all(exp.Insert):
for table in insert.find_all(exp.Table):
target_tables.add(table.name)
如果说要同时获取source和target 那就需要麻烦一点
def get_sqllineage_select(sql,db_type):
db_type = 'mysql'
source_tables = set()
target_tables = set()
for insert in parse_one(sql, read=db_type).find_all(exp.Insert):
for table in insert.find_all(exp.Table):
target_tables.add(table.name)
for select in parse_one(sql, read=db_type).find_all(exp.Select):
for table in select.find_all(exp.Table):
if table.name in target_tables: target_tables.remove(table.name)
source_tables.add(table.name)
return {'source_tables':list(source_tables),'target_tables':list(target_tables)}
为了方便使用,这里写成了一个方法。 db_type 是可以指定数据库类型的,如mysql,doris等数据库。
场景三:CTE+复杂多join查询
好在sqlglot有针对cte类型查询的类型
query = """
with tab1 as
(
select a,b from db1.table1
)
,tab2 as
(
select a from tab1
)
,tab3 as
(
select
t1.a
,t2.b
from tab1 t1
join tab2 t2
on t1.a = t2.a
)
select
*
from tab3
"""
dependencies = {}
for cte in parse_one(query).find_all(exp.CTE):
dependencies[cte.alias_or_name] = []
cte_query = cte.this.sql()
for table in parse_one(cte_query).find_all(exp.Table):
dependencies[cte.alias_or_name].append(table.name)
print(dependencies)
-- Output: {'tab1': ['table1'], 'tab2': ['tab1'], 'tab3': ['tab1', 'tab2']}
这样输出就会获取字典类型的表,他把所有的临时查询作为一个表名,输出临时表和临表里的表名。
输出结果:
{'tab1': ['table1'],
'tab2': ['tab1'],
'tab3': ['tab1', 'tab2']}
当然这不是我想要的,我想要实体表。其实很简单把上面的字典修改一下即可。
source_tables = set()
for key in dependencies:
for table in dependencies[key]:
if table not in dependencies: source_tables.add(table)
这样就能获取实体表。
场景四:insert+CTE+复杂多join查询
这种场景的sql,大部分的样子是insert在最外层,然后里面套了cte的复杂查询。
因此需要结合上面几种方式来实现。
- 解决insert的target表
- 按照CTE的当时再解析一次
- 综合以上分析分解出target和source
def get_sqllineage_cte(sql,db_type):
dependencies = {}
for cte in parse_one(sql, read="doris").find_all(exp.CTE):
dependencies[cte.alias_or_name] = []
cte_query = cte.this.sql()
for table in parse_one(sql, read="doris").find_all(exp.Table):
dependencies[cte.alias_or_name].append(table.name)
target_tables = set()
for insert in parse_one(sql, read="doris").find_all(exp.Insert):
for table in insert.find_all(exp.Table):
target_tables.add(table.name)
source_tables = set()
for select in parse_one(sql, read="doris").find_all(exp.Select):
for table in select.find_all(exp.Table):
if table.name in target_tables: target_tables.remove(table.name)
for key in dependencies:
for table in dependencies[key]:
if table not in dependencies: source_tables.add(table)
source_tables = source_tables - target_tables
return {'source_tables':list(source_tables),'target_tables':list(target_tables)}
以上场景根据我个人使用来设想的,如果你有别的场景或者发现代码问题,请多多指教。
4.总结
个人感觉sqlglot比sqlparse、sqllinage要好用一些,实际使用中遇到过在sqlparse、sqlineage中无法解析的sql,在sqlglot中依然能够解决。不过sqlglot的文档写的有点麻烦,且国内资料不多,实际使用中可能会些门槛。目前很多大项目已经在使用了,如datahub ,所以还是值得学习的。
▼关注「DataSpeed」了解更多大数据知识▼

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)