Project Icon

sqlglot

SQL解析转译优化和执行引擎

SQLGlot是一个功能全面的SQL工具,支持解析、转译、优化和执行SQL。它可以在21种SQL方言间转换,包括DuckDB、Presto和Spark等。SQLGlot能够处理多种SQL输入,并生成符合目标方言语法和语义的SQL输出。该工具采用纯Python实现,具有出色的性能和完善的测试。SQLGlot还支持自定义解析、查询分析、表达式树遍历和编程式SQL构建等高级功能。

SQLGlot标志

SQLGlot是一个无依赖的SQL解析器、转译器、优化器和引擎。它可以用于格式化SQL或在21种不同的方言之间进行转换,如DuckDBPresto / TrinoSpark / DatabricksSnowflakeBigQuery。它旨在读取各种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属性来改变这种行为。例如,我们可以将其设置为RAISEIMMEDIATE以确保引发异常:

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不支持精度

有些查询需要额外的信息才能准确转译,比如引用的表的架构。这是因为某些转换是类型敏感的,这意味着需要类型推断来理解它们的语义。尽管qualifyannotate_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很有用。此外,这个基础可以轻松地与快速计算内核集成,如ArrowPandas

下面的例子展示了执行一个涉及聚合和连接的查询:

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

另见:从零开始编写Python SQL引擎

使用者

文档

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上运行,单位为秒。

查询sqlglotsqlglotrssqlfluffsqltreesqlparsemoz_sql_parsersqloxide
tpch0.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)
short0.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)
long0.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)
crazy0.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
项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号