专业的编程技术博客社区

网站首页 > 博客文章 正文

SQLGlot:SQL解析神器,最大化/高效发挥SQL价值

baijin 2025-02-07 16:26:18 博客文章 10 ℃ 0 评论

SQL(Structured Query Language)是关系型数据库管理系统的标准语言,用于访问和操作数据库。尽管出现了新的数据管理和分析技术,但SQL仍然是企业和组织的核心工具之一。在数据科学领域,SQL易学易用且与脚本语言集成,因此被广泛应用于数据处理流程。

SQL也面临一些挑战,比如不同的数据库有不同的方言,导致转换和迁移SQL比较费力;另外,复杂的SQL格式化和优化也是很大的挑战,如何解析SQL生成数据血缘关系(表级别的、字段级别的关系)。因此,我们需要SQL解析器帮助高效完成这个工作。

SQLGlot是一个无依赖的SQL解析器、转译器、优化器和引擎。它可以用于格式化SQL或在21种不同的方言之间进行翻译,如DuckDB、Presto/Trino、Spark/Databricks、Snowflake和BigQuery。它的目标是读取各种SQL输入,并在目标方言中输出语法和语义正确的SQL。

SQLGlot是一个非常全面的通用SQL解析器,配备了强大的测试套件。而且,它在纯Python编写的同时性能也相当不错。语法错误会被突出显示,方言不兼容性可以根据配置发出警告或引发异常。

使用SQLGlot可以轻松定制解析器,分析查询,遍历表达式树,并以编程方式构建SQL。

格式化和转译

轻松实现从一种方言到另一种方言的转换。例如,不同的方言之间日期/时间函数有所不同,可能很难处理:

import sqlglot
sqlglot.transpile("SELECT EPOCH_MS(1618088028295)", read="duckdb", write="hive")[0]

SQLGlot甚至可以翻译自定义的时间格式:

import sqlglot
sqlglot.transpile("SELECT STRFTIME(x, '%y-%-m-%S')", read="duckdb", write="hive")[0]

标识符分隔符和数据类型也可以进行翻译:

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])

努力保留注释:

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
import sqlglot as sg
print(sg.transpile(sql, read='mysql', pretty=True)[0])

获取查询的元数据

比如一个典型的案例。在一个数据平台上,每天都有数百名用户发送数据库查询。有这样的需求,想知道“在某个特定时间查询了哪个表”。这个信息非常有用,比如,当想要更改某个表时,就能知道可能会受到影响的人是谁。此外,一些表可能已经很久没有被使用了,考虑减少或移除数据摄取频率,以节省云成本。

可以使用表达式助手来探索SQL,以执行诸如在查询中查找列和表等操作:

sql = """
    SELECT a.col1, a.col2, b.col3, b.col4, c.col5
    FROM Table_A a LEFT JOIN Table_B b ON a.id = b.id
"""
parsed = sg.parse_one(sql)
parsed

下面的示例提取字段名和标名:

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)

处理错误

当解析器检测到语法错误时,它会引发一个 ParseError:

import sqlglot as sg
sg.transpile("SELECT foo FROM (SELECT baz FROM t")

结构化的语法错误可供程序化使用:

import sqlglot as sg
try:
    sg.transpile("SELECT foo FROM (SELECT baz FROM t")
except sqlglot.errors.ParseError as e:
    print(e.errors)

在某些情况下,可能无法在特定方言之间转换一些查询。对于这些情况,SQLGlot会发出警告,并默认进行尽最大努力的翻译:

import sqlglot as sg
sg.transpile("SELECT APPROX_DISTINCT(a, 0.1) FROM foo", read="presto", write="hive")

构建和修改 SQL

SQLGlot 支持逐步构建 SQL 表达式:

from sqlglot import select, condition


where = condition("x=1").and_("y=1")
select("*").from_("y").where(where).sql()

修改SQL:

from sqlglot import parse_one
parse_one("SELECT x FROM y").from_("z").sql()

解析的表达式也可以通过递归地将映射函数应用到树中的每个节点来进行转换:

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()

SQL 优化器

SQLGlot 可以将查询重写为“优化”形式。它执行各种技术来创建一个新的规范 AST。这个 AST 可以用于标准化查询或为实现一个实际的引擎提供基础。例如:

import sqlglot
from sqlglot.optimizer import optimize


print(
    optimize(
        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)
)

AST 语法树

您可以通过调用 repr 来查看解析的 SQL 的 AST 版本:

from sqlglot import parse_one
print(repr(parse_one("SELECT a + 1 AS z")))

AST diff

SQLGlot 可以计算两个表达式之间的语义差异,并以一系列操作的形式输出变化,这些操作是将源表达式转换为目标表达式所需的。

from sqlglot import diff, parse_one
diff(parse_one("SELECT a + b, c, d"), parse_one("SELECT c, a - b, d"))

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
)

使用 SQLGlot 进行数据血缘解析

Metaphor 和 DataHub 都在数据血缘解析中使用了 SQLGlot。通过 SQLGlot,它们可以进行字段级别的解析,生成数据血缘图。这是 SQL 解析器的重要应用之一,旨在帮助用户了解数据在整个数据流中的传播和转换过程。

后续继续探索在数据血缘上应用实例。


本文介绍了 SQLGlot,一个功能强大的 SQL 解析器。它可以处理各种 SQL 输入,输出语法和语义正确的 SQL。SQLGlot支持格式化、转译、自省、错误处理、构建和修改 SQL、优化查询、执行和测试,并在数据血缘解析中发挥重要作用。这使得 SQLGlot 成为处理复杂数据任务的理想选择。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表