SQLGlot是一个无依赖的SQL解析器、转译器、优化器和引擎。它可以用于格式化SQL或在21种不同的方言之间进行转换,如DuckDB、Presto / Trino、Spark / Databricks、Snowflake和BigQuery。它旨在读取各种SQL输入,并在目标方言中输出语法和语义正确的SQL。
它是一个非常全面的通用SQL解析器,具有强大的测试套件。它也相当高性能,同时完全用Python编写。
你可以轻松地自定义解析器,分析查询,遍历表达式树,以及以编程方式构建 SQL。
语法错误会被突出显示,方言不兼容可以根据配置发出警告或引发异常。然而,SQLGlot并不旨在成为一个SQL验证器,因此可能无法检测某些语法错误。
在API文档和表达式树入门指南中了解更多关于SQLGlot的信息。
SQLGlot非常欢迎贡献;阅读贡献指南开始吧!
目录
安装
从PyPI安装:
pip3 install "sqlglot[rs]"
# 不带Rust分词器(较慢):
# pip3 install sqlglot
或本地checkout:
make install
开发要求(可选):
make install-dev
版本控制
给定版本号MAJOR
.MINOR
.PATCH
,SQLGlot使用以下版本策略:
- 当有向后兼容的修复或功能添加时,增加
PATCH
版本。 - 当有向后不兼容的修复或功能添加时,增加
MINOR
版本。 - 当有重大的向后不兼容的修复或功能添加时,增加
MAJOR
版本。
联系我们
我们很乐意听到你的声音。加入我们的社区Slack频道!
常见问题
我尝试解析应该有效的SQL但失败了,为什么会这样?
- 大多数情况下,这种问题发生是因为在解析过程中省略了"源"方言。例如,这是正确解析用Spark SQL编写的SQL查询的方法:
parse_one(sql, dialect="spark")
(或者:read="spark"
)。如果未指定方言,parse_one
将尝试根据"SQLGlot方言"解析查询,该方言旨在成为所有支持的方言的超集。如果你尝试指定方言后仍然不起作用,请提交一个问题。
我尝试输出SQL但它不是正确的方言!
- 与解析一样,生成SQL也需要指定目标方言,否则默认将使用SQLGlot方言。例如,要将查询从Spark SQL转译为DuckDB,执行
parse_one(sql, dialect="spark").sql(dialect="duckdb")
(或者:transpile(sql, read="spark", write="duckdb")
)。
我尝试解析无效的SQL,它成功了,尽管应该引发错误!为什么它没有验证我的SQL?
- SQLGlot并不旨在成为一个SQL验证器 - 它被设计得非常宽容。这使得代码库更加全面,也为用户提供了更多灵活性,例如允许他们在投影列表中包含尾随逗号。
sqlglot.dataframe发生了什么?
- PySpark数据框API在第24版中被移到了一个独立的库SQLFrame中。它现在允许你运行查询,而不仅仅是生成SQL。
示例
格式化和转译
轻松地从一种方言转换到另一种方言。例如,日期/时间函数在不同方言之间有所不同,可能很难处理:
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: 期望)。第1行,第34列。
SELECT foo FROM (SELECT baz FROM t
~
结构化的语法错误可用于程序化使用:
import sqlglot
try:
sqlglot.transpile("SELECT foo FROM (SELECT baz FROM t")
except sqlglot.errors.ParseError as e:
print(e.errors)
[{
'description': '期望)',
'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不支持精度
'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不支持精度
有些查询需要额外的信息才能准确转译,比如引用的表的架构。这是因为某些转换是类型敏感的,这意味着需要类型推断来理解它们的语义。尽管qualify
和annotate_types
优化器规则可以帮助解决这个问题,但它们默认不被使用,因为它们会增加显著的开销和复杂性。
转译通常是一个困难的问题,所以SQLGlot采用"增量"方法来解决它。这意味着目前可能存在一些方言对无法支持某些输入,但预计随着时间的推移会有所改善。我们非常感谢有良好文档和测试的问题或PR,如果你需要指导,请随时联系我们!
构建和修改SQL
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'
SQL优化器
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
```python
[
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'>
SQL执行
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 # 完整的测试套件和代码检查
基准测试
基准测试在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) |
可选依赖
SQLGlot使用dateutil来简化字面时间间隔表达式。如果找不到该模块,优化器将不会简化像下面这样的表达式:
x + interval '1' month