跳转到主要内容

一个易于定制的SQL解析器和翻译器

项目描述

SQLGlot logo

SQLGlot是一个无依赖的SQL解析器、翻译器、优化器和引擎。它可以用来格式化SQL或在不同23种方言之间进行转换,如DuckDBPresto / TrinoSpark / DatabricksSnowflakeBigQuery。它的目标是读取各种SQL输入,并在目标方言中输出语法和语义正确的SQL。

它是一个非常全面的通用SQL解析器,具有健壮的测试套件。它也非常高效,完全用Python编写。

您可以轻松自定义解析器,分析查询,遍历表达式树,并以编程方式构建 SQL。

语法错误将被突出显示,方言不兼容性将根据配置发出警告或引发错误。然而,SQLGlot并不旨在成为SQL验证器,因此它可能无法检测到某些语法错误。

在API文档和表达式树入门中了解更多关于SQLGlot的信息。

SQLGlot非常欢迎贡献;阅读贡献指南入职文档开始贡献吧!

目录

安装

从PyPI安装

pip3 install "sqlglot[rs]"

# Without Rust tokenizer (slower):
# pip3 install sqlglot

或者使用本地检出

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 dataframe API在v24版本中被移到了一个名为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 requires backticks (`) for delimited identifiers and uses `FLOAT` over `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"""

# Translates the query into Spark SQL, formats it, and delimits all of its identifiers
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 = """
/* multi
   line
   comment
*/
SELECT
  tbl.cola /* comment 1 */ + tbl.colb /* comment 2 */,
  CAST(x AS SIGNED), # comment 3
  y               -- comment 4
FROM
  bar /* comment 5 */,
  tbl #          comment 6
"""

# Note: MySQL-specific comments (`#`) are converted into standard syntax
print(sqlglot.transpile(sql, read='mysql', pretty=True)[0])
/* multi
   line
   comment
*/
SELECT
  tbl.cola /* comment 1 */ + tbl.colb /* comment 2 */,
  CAST(x AS INT), /* comment 3 */
  y /* comment 4 */
FROM bar /* comment 5 */, tbl /*          comment 6 */

元数据

您可以使用表达式辅助工具来探索SQL,例如在查询中查找列和表

from sqlglot import parse_one, exp

# print all column references (a and b)
for column in parse_one("SELECT a, b + 1 AS c FROM d").find_all(exp.Column):
    print(column.alias_or_name)

# find all projections in select statements (a and 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)

# find all tables (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
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 属性来改变此行为。例如,我们可以将其设置为 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 does not support accuracy

有一些查询需要额外的信息才能准确转换,例如它们引用的表的架构。这是因为某些转换是类型敏感的,这意味着需要类型推断才能理解它们的语义。尽管 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": "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)

AST内省

通过调用 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))])

AST差异

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'>

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  # Only linter checks
make unit   # Only unit tests (or unit-rs, to use the Rust tokenizer)
make test   # Unit and integration tests (or test-rs, to use the Rust tokenizer)
make check  # Full test suite & linter checks

基准测试

基准测试 在 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 来简化 timedelta 文字表达式。优化器不会简化以下表达式

x + interval '1' month

项目详情


发布历史 发布通知 | RSS 源

下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。

源代码分发

sqlglot-25.24.4.tar.gz (19.5 MB 查看哈希值)

上传时间 源代码

构建分发

sqlglot-25.24.4-py3-none-any.whl (417.4 kB 查看哈希值)

上传时间 Python 3

支持