用 SQLGlot 打通多数据库:SQL 解析器 3 大核心能力与跨库迁移实战指南

用 SQLGlot 打通多数据库:SQL 解析器 3 大核心能力与跨库迁移实战指南

用 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.nameorders.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),仅供参考