SQLGlot解析器添加福利群:解决AI开发者的「MCP实战痛点」
SQLGlot 是一个无依赖的 SQL 解析器、转译器、优化器和执行引擎。它可用于格式化 SQL 或在 31 种不同方言 之间进行转换,如 DuckDB、Presto / Trino、Spark / Databricks、Snowflake 和 BigQuery。它旨在读取各种 SQL 输入,并以目标方言输出语法和语义正确的 SQL。
它是一个非常全面的通用 SQL 解析器,具有健壮的 测试套件。虽然完全用 Python 编写,但它的 性能 也相当出色。
您可以轻松地 自定义 解析器、分析 查询、遍历表达式树并以编程方式 构建 SQL。
SQLGlot 可以检测多种 语法错误,例如不匹配的括号、保留关键字的不正确使用等。这些错误会被高亮显示,并且根据配置,方言不兼容性可以警告或引发错误。
在 API 文档 和表达式树 入门指南 中了解更多关于 SQLGlot 的信息。
SQLGlot 非常欢迎贡献;阅读 贡献指南 和 入门文档 开始吧!
从 PyPI 安装:
pip3 install "sqlglot[rs]"
# 不使用 Rust 分词器(较慢):
# pip3 install sqlglot

或从本地检出安装:
# 可选前缀 UV=1 以使用 uv 进行安装 make install
开发需求(可选):
# 可选前缀 UV=1 以使用 uv 进行安装 make install-dev
给定版本号 MAJOR.MINOR.PATCH,SQLGlot 使用以下版本控制策略:
PATCH 版本在有向后兼容的修复或功能添加时递增。MINOR 版本在有向后不兼容的修复或功能添加时递增。MAJOR 版本在有重大向后不兼容的修复或功能添加时递增。我们很乐意听取您的意见。加入我们的社区 Slack 频道!
我尝试解析应该是有效的 SQL,但失败了,为什么会这样?
parse_one(sql, dialect="spark")(或者:read="spark")。如果未指定方言,parse_one 将尝试根据“SQLGlot 方言”解析查询,该方言设计为所有支持方言的超集。如果您已指定方言但仍无法工作,请提交问题。我尝试输出 SQL,但它不是正确的方言!
parse_one(sql, dialect="spark").sql(dialect="duckdb")(或者:transpile(sql, read="spark", write="duckdb"))。sqlglot.dataframe 发生了什么?
轻松在方言之间转换。例如,日期/时间函数因方言而异,可能难以处理:
import sqlglot
sqlglot.transpile("SELECT EPOCH_MS(1618088028295)", read="duckdb", write="hive")[0]

'SELECT FROM_UNIXTIME(1618088028295 / POW(10, 3))'

SQLGlot 甚至可以转换自定义时间格式:
import sqlglot
sqlglot.transpile("SELECT STRFTIME(x, '%y-%-m-%S')", read="duckdb", write="hive")[0]

"SELECT DATE_FORMAT(x, 'yy-M-ss')"
标识符分隔符和数据类型也可以转换:
import sqlglot
# Spark SQL 要求用反引号 (`) 分隔标识符,并使用 `FLOAT` 而不是 `REAL`
sql = """WITH baz AS (SELECT a, c FROM foo WHERE a = 1) SELECT f.a, b.b, baz.c, CAST("b"."a" AS REAL) d FROM foo f JOIN bar b ON f.a = b.a LEFT JOIN baz ON f.a = baz.a"""
# 将查询转换为 Spark SQL,格式化并分隔所有标识符
print(sqlglot.transpile(sql, write="spark", identify=True, pretty=True)[0])

WITH `baz` AS (
SELECT
`a`,
`c`
FROM `foo`
WHERE
`a` = 1
)
SELECT
`f`.`a`,
`b`.`b`,
`baz`.`c`,
CAST(`b`.`a` AS FLOAT) AS `d`
FROM `foo` AS `f`
JOIN `bar` AS `b`
ON `f`.`a` = `b`.`a`
LEFT JOIN `baz`
ON `f`.`a` = `baz`.`a`

注释也会尽可能保留:
sql = """
/* 多
行
注释
*/
SELECT
tbl.cola /* 注释 1 */ + tbl.colb /* 注释 2 */,
CAST(x AS SIGNED), # 注释 3
y -- 注释 4
FROM
bar /* 注释 5 */,
tbl # 注释 6
"""
# 注意:MySQL 特有的注释 (`#`) 被转换为标准语法
print(sqlglot.transpile(sql, read='mysql', pretty=True)[0])

/* 多
行
注释
*/
SELECT
tbl.cola /* 注释 1 */ + tbl.colb /* 注释 2 */,
CAST(x AS INT), /* 注释 3 */
y /* 注释 4 */
FROM bar /* 注释 5 */, tbl /* 注释 6 */

您可以使用表达式助手探索 SQL,例如查找查询中的列和表:
from sqlglot import parse_one, exp
# 打印所有列引用(a 和 b)
for column in parse_one("SELECT a, b + 1 AS c FROM d").find_all(exp.Column):
print(column.alias_or_name)
# 查找所有 SELECT 语句中的投影(a 和 c)
for select in parse_one("SELECT a, b + 1 AS c FROM d").find_all(exp.Select):
for projection in select.expressions:
print(projection.alias_or_name)
# 查找所有表(x, y, z)
for table in parse_one("SELECT * FROM x JOIN y JOIN z").find_all(exp.Table):
print(table.name)

阅读 AST 入门指南 以了解更多关于 SQLGlot 内部的信息。
当解析器检测到语法错误时,会引发 ParseError:
import sqlglot
sqlglot.transpile("SELECT foo FROM (SELECT baz FROM t")

sqlglot.errors.ParseError: Expecting ). Line 1, Col: 34. SELECT foo FROM (SELECT baz FROM t ~
结构化语法错误可供编程使用:
import sqlglot.errors
try:
sqlglot.transpile("SELECT foo FROM (SELECT baz FROM t")
except sqlglot.errors.ParseError as e:
print(e.errors)

[{
'description': 'Expecting )',
'line': 1,
'col': 34,
'start_context': 'SELECT foo FROM (SELECT baz FROM ',
'highlight': 't',
'end_context': '',
'into_expression': None
}]

某些查询可能无法在某些方言之间转换。对于这些情况,SQLGlot 可能会发出警告,并默认进行尽力而为的转换:
import sqlglot
sqlglot.transpile("SELECT APPROX_DISTINCT(a, 0.1) FROM foo", read="presto", write="hive")

APPROX_COUNT_DISTINCT does not support accuracy
'SELECT APPROX_COUNT_DISTINCT(a) FROM foo'

可以通过设置 unsupported_level 属性来更改此行为。例如,我们可以将其设置为 RAISE 或 IMMEDIATE 以确保引发异常:
import sqlglot
sqlglot.transpile("SELECT APPROX_DISTINCT(a, 0.1) FROM foo", read="presto", write="hive", unsupported_level=sqlglot.ErrorLevel.RAISE)

sqlglot.errors.UnsupportedError: APPROX_COUNT_DISTINCT does not support accuracy
有些查询需要额外的信息才能准确转译,例如它们引用的表的模式。这是因为某些转换是类型敏感的,意味着需要类型推断才能理解其语义。尽管 qualify 和 annotate_types 优化器 规则 可以帮助解决这个问题,但它们默认不使用,因为它们增加了显著的开销和复杂性。
转译通常是一个难题,因此 SQLGlot 采用“增量”方法来解决它。这意味着目前可能有些方言对缺少某些输入的支持,但预计随着时间的推移会有所改进。我们非常欢迎有详细文档和测试的问题或 PR,因此如果您需要指导,请随时 联系我们!
SQLGlot支持增量式构建SQL表达式:
from sqlglot import select, condition
where = condition("x=1").and_("y=1")
select("*").from_("y").where(where).sql()

'SELECT * FROM y WHERE x = 1 AND y = 1'

可以修改已解析的语法树:
from sqlglot import parse_one
parse_one("SELECT x FROM y").from_("z").sql()

'SELECT x FROM z'

通过向语法树中的每个节点应用映射函数,还可以递归转换已解析的表达式:
from sqlglot import exp, parse_one
expression_tree = parse_one("SELECT a FROM x")
def transformer(node):
if isinstance(node, exp.Column) and node.name == "a":
return parse_one("FUN(a)")
return node
transformed_tree = expression_tree.transform(transformer)
transformed_tree.sql()

'SELECT FUN(a) FROM x'

SQLGlot能将查询重写为"优化"形式。它采用多种技术创建新的规范AST。该AST可用于标准化查询或为实际引擎实现奠定基础。例如:
import sqlglot
from sqlglot.optimizer import optimize
print(
optimize(
sqlglot.parse_one("""
SELECT A OR (B OR (C AND D))
FROM x
WHERE Z = date '2021-01-01' + INTERVAL '1' month OR 1 = 0
"""),
schema={"x": {"A": "INT", "B": "INT", "C": "INT", "D": "INT", "Z": "STRING"}}
).sql(pretty=True)
)

SELECT
(
"x"."a" <> 0 OR "x"."b" <> 0 OR "x"."c" <> 0
)
AND (
"x"."a" <> 0 OR "x"."b" <> 0 OR "x"."d" <> 0
) AS "_col_0"
FROM "x" AS "x"
WHERE
CAST("x"."z" AS DATE) = CAST('2021-02-01' AS DATE)

通过调用repr可以查看解析后SQL的AST版本:
from sqlglot import parse_one
print(repr(parse_one("SELECT a + 1 AS z")))

Select(
expressions=[
Alias(
this=Add(
this=Column(
this=Identifier(this=a, quoted=False)),
expression=Literal(this=1, is_string=False)),
alias=Identifier(this=z, quoted=False))])

SQLGlot能计算两个表达式间的语义差异,并以需要执行的操作序列形式输出变更,这些操作用于将源表达式转换为目标表达式:
from sqlglot import diff, parse_one
diff(parse_one("SELECT a + b, c, d"), parse_one("SELECT c, a - b, d"))

[
Remove(expression=Add(
this=Column(
this=Identifier(this=a, quoted=False)),
expression=Column(
this=Identifier(this=b, quoted=False)))),
Insert(expression=Sub(
this=Column(
this=Identifier(this=a, quoted=False)),
expression=Column(
this=Identifier(this=b, quoted=False)))),
Keep(
source=Column(this=Identifier(this=a, quoted=False)),
target=Column(this=Identifier(this=a, quoted=False))),
...
]

另见:SQL语义差异比较
可通过继承Dialect类来添加方言:
from sqlglot import exp
from sqlglot.dialects.dialect import Dialect
from sqlglot.generator import Generator
from sqlglot.tokens import Tokenizer, TokenType
class Custom(Dialect):
class Tokenizer(Tokenizer):
QUOTES = ["'", '"']
IDENTIFIERS = ["`"]
KEYWORDS = {
**Tokenizer.KEYWORDS,
"INT64": TokenType.BIGINT,
"FLOAT64": TokenType.DOUBLE,
}
class Generator(Generator):
TRANSFORMS = {exp.Array: lambda self, e: f"[{self.expressions(e)}]"}
TYPE_MAPPING = {
exp.DataType.Type.TINYINT: "INT64",
exp.DataType.Type.SMALLINT: "INT64",
exp.DataType.Type.INT: "INT64",
exp.DataType.Type.BIGINT: "INT64",
exp.DataType.Type.DECIMAL: "NUMERIC",
exp.DataType.Type.FLOAT: "FLOAT64",
exp.DataType.Type.DOUBLE: "FLOAT64",
exp.DataType.Type.BOOLEAN: "BOOL",
exp.DataType.Type.TEXT: "STRING",
}
print(Dialect["custom"])

<class '__main__.Custom'>
SQLGlot能解释执行SQL查询,其中表用Python字典表示。该引擎不追求速度,但可用于单元测试和在Python对象间原生运行SQL。此外,该基础可轻松集成快速计算内核,如Arrow和Pandas。
以下示例展示了一个涉及聚合和连接的查询执行:
from sqlglot.executor import execute
tables = {
"sushi": [
{"id": 1, "price": 1.0},
{"id": 2, "price": 2.0},
{"id": 3, "price": 3.0},
],
"order_items": [
{"sushi_id": 1, "order_id": 1},
{"sushi_id": 1, "order_id": 1},
{"sushi_id": 2, "order_id": 1},
{"sushi_id": 3, "order_id": 2},
],
"orders": [
{"id": 1, "user_id": 1},
{"id": 2, "user_id": 2},
],
}
execute(
"""
SELECT
o.user_id,
SUM(s.price) AS price
FROM orders o
JOIN order_items i
ON o.id = i.order_id
JOIN sushi s
ON i.sushi_id = s.id
GROUP BY o.user_id
""",
tables=tables
)

user_id price
1 4.0
2 3.0

SQLGlot使用pdoc提供API文档。
托管版本在SQLGlot网站上,也可本地构建:
make docs-serve
make style # 仅代码检查 make unit # 仅单元测试(或unit-rs使用Rust分词器) make test # 单元和集成测试(或test-rs使用Rust分词器) make check # 完整测试套件和代码检查
部署新版本SQLGlot的步骤:
git pull确保本地git仓库在main分支头部sqlglotrs/Cargo.toml的package头下增加version属性make install-dev更新Cargo.lock文件Cargo.toml和Cargo.lock的更改git tag操作增加版本号,如git tag v28.5.0git push && git push --tags部署新版本[!重要] 如果自上次发布后有破坏性变更,请确保为sqlglot和sqlglotrs发布次要或主要版本。参考SQLGlot的版本控制方案获取更多信息。
基准测试在Python 3.10.12环境下运行(单位:秒)。
| 查询 | sqlglot | sqlglotrs | sqlfluff | sqltree | sqlparse | moz_sql_parser | sqloxide |
|---|---|---|---|---|---|---|---|
| tpch | 0.00944 (1.0) | 0.00590 (0.625) | 0.32116 (33.98) | 0.00693 (0.734) | 0.02858 (3.025) | 0.03337 (3.532) | 0.00073 (0.077) |
| short | 0.00065 (1.0) | 0.00044 (0.687) | 0.03511 (53.82) | 0.00049 (0.759) | 0.00163 (2.506) | 0.00234 (3.601) | 0.00005 (0.073) |
| long | 0.00889 (1.0) | 0.00572 (0.643) | 0.36982 (41.56) | 0.00614 (0.690) | 0.02530 (2.844) | 0.02931 (3.294) | 0.00059 (0.066) |
| crazy | 0.02918 (1.0) | 0.01991 (0.682) | 1.88695 (64.66) | 0.02003 (0.686) | 7.46894 (255.9) | 0.64994 (22.27) | 0.00327 (0.112) |
make bench # 运行解析基准测试 make bench-optimize # 运行优化基准测试
SQLGlot使用dateutil简化字面时间间隔表达式。如果找不到该模块,优化器将不会简化如下表达式:
x + interval '1' month

| 方言 | 支持级别 |
|---|---|
| Athena | 官方支持 |
| BigQuery | 官方支持 |
| ClickHouse | 官方支持 |
| Databricks | 官方支持 |
| Doris | 社区支持 |
| Dremio | 社区支持 |
| Drill | 社区支持 |
| Druid | 社区支持 |
| DuckDB | 官方支持 |
| Exasol | 社区支持 |
| Fabric | 社区支持 |
| Hive | 官方支持 |
| Materialize | 社区支持 |
| MySQL | 官方支持 |
| Oracle | 官方支持 |
| Postgres | 官方支持 |
| Presto | 官方支持 |
| PRQL | 社区支持 |
| Redshift | 官方支持 |
| RisingWave | 社区支持 |
| SingleStore | 社区支持 |
| Snowflake | 官方支持 |
| Solr | 社区支持 |
| Spark | 官方支持 |
| SQLite | 官方支持 |
| StarRocks | 官方支持 |
| Tableau | 官方支持 |
| Teradata | 社区支持 |
| Trino | 官方支持 |
| TSQL | 官方支持 |
| YDB | 插件 |
官方方言由核心SQLGlot团队维护,享有更高的错误修复和功能添加优先级。
社区方言主要由社区贡献者开发和维护。这些方言功能完整,但问题解决优先级可能低于官方支持方言。我们欢迎并鼓励社区贡献以改进这些方言。
插件方言(自v28.6.0起支持)是由独立贡献者在外部代码库中开发和维护的第三方方言。这些方言不属于SQLGlot代码库的一部分,而是作为独立的软件包分发。SQLGlot团队不为插件方言提供支持或维护——如有任何问题或功能需求,请直接联系其对应的代码库。关于如何构建自己的方言插件,请参阅下文创建方言插件部分。
如果您的数据库不受支持,您可以创建一个插件,通过入口点注册自定义方言。创建一个包含您的方言类的软件包,并在setup.py中进行注册:
from setuptools import setup
setup(
name="mydb-sqlglot-dialect",
entry_points={
"sqlglot.dialects": [
"mydb = my_package.dialect:MyDB",
],
},
)

该方言将被自动发现,并可以像任何内置方言一样使用:
from sqlglot import transpile
transpile("SELECT * FROM t", read="mydb", write="postgres")

有关实现细节,请参阅自定义方言部分。