用 SQLGlot 打通多数据库:SQL 解析器 3 大核心能力与跨库迁移实战指南
【免费下载链接】sqlglotPython SQL Parser and Transpiler项目地址: https://gitcode.com/gh_mirrors/sq/sqlglot
想象一下这样的场景:老板把一份写着 MySQL 语法的查询丢给你,要求立刻在 BigQuery 上跑出结果。你把
DATE_FORMAT改成FORMAT_TIMESTAMP,把IFNULL换成IFNULL……改了十处,跑起来又报三个错。跨数据库迁移的痛苦,经历过的人都懂。而今天要介绍的SQLGlot,就是一个能让你从这种痛苦中解脱出来的 Python 工具——它把 33 种 SQL 方言的互相转换、语法检查、查询优化统统打包成了一个零依赖的库,你只需要两行代码,就能完成过去需要半天手改的工作。
从翻译官说起:SQLGlot 到底是什么
你可以把 SQLGlot 想象成一位精通 33 种"数据库方言"的随身翻译官。你给它一句用"方言 A"写的 SQL,它先读懂这句话想表达什么(而不是死记硬背字符串),再用"方言 B"重新说一遍。
关键就在"先读懂"这一步:SQLGlot 会把 SQL 解析成一棵抽象语法树(AST)——一种用节点表示"选择了哪几列、从哪个表、怎么连接、怎么过滤"的树状结构。一旦 SQL 变成了结构化的树,后续的转译、格式化、优化、血缘分析就都变成了"操作这棵树",而不是"处理字符串"。这也解释了为什么它叫 SQLGlot——Glot 取自 polyglot(通晓多国语言的人)。
3 分钟快速上手:安装与最小示例
SQLGlot 是纯 Python 实现、零第三方依赖,安装非常简单:
pip3 install sqlglot装好后,先跑一个最小示例确认环境没问题:
import sqlglot # 从 DuckDB 方言转译到 Hive 方言 print(sqlglot.transpile("SELECT EPOCH_MS(1618088028295)", read="duckdb", write="hive")) # 输出:['SELECT FROM_UNIXTIME(1618088028295 / POW(10, 3))']这段代码解决的是"同一个查询在不同数据库上跑"的问题:EPOCH_MS是 DuckDB 的函数,SQLGlot 自动把它翻译成了 Hive 认识的FROM_UNIXTIME,连时间精度换算(除以 10 的 3 次方)都帮你处理好了。
核心能力一:多方言 SQL 转译,写一次到处跑
转译是 SQLGlot 最出圈的能力。官方支持 33 种方言:DuckDB、Presto/Trino、Spark/Databricks、Snowflake、BigQuery、MySQL、PostgreSQL、ClickHouse……基本覆盖了主流数仓和 OLTP 数据库。
转译的核心是transpile函数,它接收一段 SQL、指定"读入方言"和"写出方言",返回一个列表(因为一段脚本可能包含多条语句):
import sqlglot # 从 MySQL 转换到 PostgreSQL result = sqlglot.transpile( "SELECT DATE_FORMAT(created_at, '%Y-%m-%d') FROM users", read="mysql", write="postgres", ) print(result) # 输出:["SELECT TO_CHAR(CAST(created_at AS TIMESTAMP), 'YYYY-MM-DD') FROM users"]注意看,SQLGlot 不只是替换函数名,它把 MySQL 的DATE_FORMAT完整重构成了 PostgreSQL 的TO_CHAR,还自动补上了CAST类型转换。再比如 Snowflake 的DATE_TRUNC('month', created_at)转成 BigQuery 就是TIMESTAMP_TRUNC(created_at, MONTH),参数顺序都帮你调整好了。
实际价值:多数据源汇聚分析、数仓上云迁移、BI 工具 SQL 适配——这类"写一次、处处跑"的需求,用transpile写一个循环就能批量处理:
queries = [ "SELECT DATE_FORMAT(created_at, '%Y-%m-%d') FROM users", "SELECT EPOCH_MS(1618088028295)", ] for sql in queries: print(sqlglot.transpile(sql, read="mysql", write="bigquery"))核心能力二:操作抽象语法树,像摆弄字典一样摆弄 SQL
如果说转译是"开箱即用",那 AST 操作就是 SQLGlot 真正强大的地方。解析得到的语法树是一个普通 Python 对象,你可以遍历它、搜索它、修改它。
用parse_one把 SQL 变成 AST,然后遍历找出所有列:
import sqlglot from sqlglot import exp ast = sqlglot.parse_one("SELECT a FROM (SELECT a FROM t) AS x") # 遍历 AST 的所有节点 for node in ast.walk(): if isinstance(node, exp.Column): print(f"找到列: {node.name}")这段代码解决的问题是"我怎么从一段 SQL 里提取出所有引用的列"——这正是做 SQL 静态分析、数据字典盘点、权限控制的基础。
除了遍历,你还能直接生成美化后的 SQL。工作中常见的"一段压成一行、完全没有缩进的 SQL",用一行代码就能格式化:
from sqlglot import parse_one ugly_sql = "SELECT * FROM users WHERE age>18 ORDER BY created_at DESC" print(parse_one(ugly_sql).sql(pretty=True, identify=True))pretty=True负责换行缩进,identify=True会给标识符加上双引号,输出立刻变成可读性很高的标准格式。想进一步了解 AST 的节点类型和遍历方法,可以看仓库里的入门文档 posts/ast_primer.md。
核心能力三:内置优化器,自动重写查询提升性能
SQLGlot 不只是"看懂"SQL,它还能"改进"SQL。optimizer.optimize会执行一系列规则:去掉冗余子查询、合并可合并的 JOIN、把过滤条件下推、自动限定列名、消除未使用的列等。
下面这段代码,把一条手写的查询交给优化器处理:
from sqlglot import parse_one from sqlglot.optimizer import optimize sql = """ SELECT users.name, orders.total FROM users JOIN orders ON users.id = orders.user_id WHERE orders.created_at > '2024-01-01' GROUP BY users.name """ optimized = optimize(parse_one(sql)) print(optimized.sql(pretty=True))优化后的结果会看到两个明显变化:一是所有列名都被自动加上了表名前缀(users.name、orders.user_id),消除了歧义;二是WHERE里的过滤条件orders.created_at > ...被下推到了 JOIN 的ON子句中,让数据库能更早地过滤数据、减少 JOIN 的中间结果。
实际价值:在把查询分发给底层数据库执行之前先过一遍优化器,等于给你的查询做了一次免费"预编译优化",尤其适合数据平台类产品对用户提交的 SQL 做预处理。
实战演练:数据血缘分析与 SQL 差异检查
前面学的转译、AST、优化已经足够应付日常,但 SQLGlot 还有两个杀器:数据血缘分析和SQL 差异比较,它们在数据治理和 CI/CD 场景里非常有用。
追踪列的血缘:一个查询读懂数据流向
sqlglot.lineage.lineage能追踪某个列从源头表到最终结果的完整传递路径。下面这段代码,把一条含 CTE 的查询中traced_col列的来龙去脉查出来:
from sqlglot.lineage import lineage result = lineage( "traced_col", "WITH cte AS (SELECT traced_col FROM intermediate) SELECT traced_col FROM cte", dialect="duckdb", ) print(result)它会告诉你这个列是从哪个根表、经过哪些中间层一路流动过来的。下图展示了典型的列血缘链路:数据从底部的root_table出发,经过中间表,最终到达顶部的 CTE 输出。
比较两个 SQL:精准定位逻辑差异
在代码评审或 SQL 版本迭代时,你常常想知道"这条 SQL 改了什么"。SQLGlot 的diff模块通过对比两棵 AST 的结构差异来实现:
from sqlglot import diff, parse_one changes = diff( parse_one("SELECT a, b FROM t"), parse_one("SELECT a, c FROM t"), ) print(changes) # 输出包含 Remove(列 b) 和 Insert(列 c) 等变更记录它不依赖字符串比对,而是基于语法树节点的语义映射——所以哪怕只是换了个别名、调整了括号,都能正确识别"其实没变"。
实战价值:把血缘分析接进数据质量平台、把 diff 接进 CI 流程,就能自动生成"本次发布改了哪些表的哪些列"的变更清单,这是手工 Review 很难做到的。
避坑指南:新手最容易踩的 5 个坑
import sqlglot from sqlglot import parse_one from sqlglot.optimizer.qualify import qualify # 坑1:read / write 写反了 # 结果会变成"用目标方言读、用源方言写",报错或乱输出 sqlglot.transpile("SELECT 1", read="mysql", write="bigquery") # 坑2:忽略转译返回的是列表 # transpile 返回 list,直接当字符串用会报错 result = sqlglot.transpile("SELECT 1", read="mysql", write="bigquery")[0]| 常见问题 | 表现 | 解决办法 |
|---|---|---|
| read / write 参数颠倒 | 语法报错或输出混乱 | 记住"read 读入方言、write 写出方言" |
忘了[0]取列表元素 | 拿到 list 而非字符串 | transpile(...)[0] |
| 未捕获 ParseError | 括号不平衡等错误直接抛异常 | 用try/except sqlglot.errors.ParseError包住 |
| 优化后列名带前缀"变样" | 查询结果似乎多了别名 | 这是 qualify 的预期行为,可关闭qualify相关规则 |
| 语法不合法但没报错 | 转译结果为空或奇怪 | 先parse_one检查能否解析 |
错误处理的推荐写法:
try: sqlglot.transpile("SELECT foo FROM (SELECT baz FROM t") except sqlglot.errors.ParseError as e: print(f"SQL 语法错误: {e}")总结与下一步
回顾一下本文的核心内容:
- 多方言转译:
transpile一行代码在 33 种方言间自由转换,函数、类型、参数顺序都帮你调整 - AST 操作:
parse_one把 SQL 变成可遍历、可修改、可美化的树结构,是静态分析的基石 - 查询优化:
optimize自动下推条件、限定列名、去冗余,免费给你的查询做预编译优化 - 进阶能力:
lineage做列级血缘分析、diff做 AST 级差异比较 - 避坑要点:read/write 方向、返回值类型、异常捕获是新手最常踩的坑
SQLGlot 适合这几类人:要做跨库迁移或 SQL 适配的数据工程师、想构建 SQL 静态分析工具的平台开发者、需要对查询做自动改写优化的后端工程师,以及想深入理解 SQL 解析原理的学习者。
想继续深入,可以从这几处入手:转译实现看sqlglot/generators/目录,解析规则看sqlglot/parsers/目录,优化器规则在sqlglot/optimizer/目录,每个模块都有清晰的边界和注释,非常适合当源码教材来读。
现在,打开你的终端,pip3 install sqlglot,把你手头那条最折磨人的跨库查询丢给transpile试试——你会发现,那些曾经让人通宵的语法差异,其实一行代码就能解决。
【免费下载链接】sqlglotPython SQL Parser and Transpiler项目地址: https://gitcode.com/gh_mirrors/sq/sqlglot
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考