返回

Python 解析 SQL 查询提取字段 Derivation:构建数据 Lineage 的关键

python

在数据分析和数据治理领域,了解数据 lineage 非常重要。数据 lineage 可以追踪数据从源头到最终结果的整个生命周期,就像产品的生产履历一样,让我们清楚地知道数据是怎么来的,经过了哪些加工处理。而 SQL 查询作为数据处理的重要一环,解析其每个字段的 derivation(来源)就成为了构建数据 lineage 的关键步骤。

本文将探讨如何使用 Python 从 SQL SELECT 查询中提取每个字段的 derivation 信息。我们会从一个实际问题出发,分析 SQL 解析的挑战,并逐步构建一个解决方案。

问题背景

假设你面对一个复杂的 SQL 查询,其中包含各种嵌套的函数、子查询和 CASE WHEN 语句。你想要清晰地了解每个输出字段是如何计算出来的,它依赖哪些原始字段,以及经历了哪些转换过程。

举个例子,我们有以下 SQL 查询:

SELECT
    col1 AS c1,
    SUBSTR(
      CONCAT(
          CASE
              WHEN UPPER(RTRIM(M.col2)) IN('C', 'P') 
                  THEN 'P'
              WHEN M.col3 IS NULL 
                  THEN M.col4
              ELSE 
                  CASE
                      WHEN UPPER(RTRIM(M.col5)) IN('', 'D') 
                                OR M.col6 IS NULL 
                          THEN 'D'
                      WHEN UPPER(RTRIM(M.col7)) IN('C', 'P') 
                          THEN 'P'
                      ELSE CAST(NULL AS STRING)
                  END
          END, 
        ' '), 1, 1
    ) AS c2,
    SUBSTR(
        CONCAT(
            SUBSTR(CAST(col8 AS STRING), 1, 5), 
            '|', COALESCE(col9, 'NULL')
        ), 1, 100
    ) AS c3,
    SUBSTR(CONCAT('test', '   '), 1, 3) AS c4,
    SUBSTR('test', 1, 10) AS c5
    ...

我们希望得到类似以下的输出,展示每个字段的 derivation:

  • c1 : col1
  • c2 :
    SUBSTR(
        CONCAT(
            CASE
                WHEN UPPER(RTRIM(M.col2)) IN('C', 'P') 
                    THEN 'P'
                WHEN M.col3 IS NULL 
                    THEN M.col4
                ELSE 
                    CASE
                        WHEN UPPER(RTRIM(M.col5)) IN('', 'D')
                                OR M.col6 IS NULL 
                            THEN 'D'
                        WHEN UPPER(RTRIM(M.col7)) IN('C', 'P') 
                            THEN 'P'
                        ELSE CAST(NULL AS STRING)
                    END
            END, 
        ' '), 1, 1
    ) 
  • c3 :
    SUBSTR(
        CONCAT(
            SUBSTR(CAST(col8 AS STRING), 1, 5), 
                '|', 
            COALESCE(col9, 'NULL')
        ), 1, 100
    ) 
  • c4 : SUBSTR(CONCAT('test', ' '), 1, 3)
  • c5 : SUBSTR('test', 1, 10)

解决方案

我们可以利用 Python 的一些库来实现这个目标。sqlparse 库可以帮助我们解析 SQL 语句,将其分解成不同的部分,比如 SELECT 子句、FROM 子句、WHERE 子句等。然后,我们可以遍历 SELECT 子句中的每个字段,提取其表达式部分,并进行一些处理,比如去除别名和多余的空格。

以下是一个 Python 代码示例,演示了如何使用 sqlparse 库提取字段 derivation:

import sqlparse

def extract_derivation(sql_query):
    parsed = sqlparse.parse(sql_query)[0]
    select_stmt = parsed.tokens[2]  # 假设 SELECT 是第三个 token
    derivations = {}
    for token in select_stmt.tokens:
        if isinstance(token, sqlparse.sql.IdentifierList):
            for identifier in token.get_identifiers():
                column_name = identifier.get_name()
                derivation = str(identifier.get_real_name())
                derivations[column_name] = derivation.strip()
        elif isinstance(token, sqlparse.sql.Identifier):
            column_name = token.get_name()
            derivation = str(token.get_real_name())
            derivations[column_name] = derivation.strip()
    return derivations

# 示例用法
sql_query = """
SELECT
    col1 AS c1,
    SUBSTR(CONCAT('test', '   '), 1, 3) AS c4,
    ...
"""
derivations = extract_derivation(sql_query)
for column_name, derivation in derivations.items():
    print(f"{column_name} - {derivation}")

这段代码首先使用 sqlparse.parse 函数解析 SQL 查询,然后找到 SELECT 子句。接着,它遍历 SELECT 子句中的每个 token,如果是 IdentifierListIdentifier 类型,就提取字段名和表达式,并存储到 derivations 字典中。最后,它打印每个字段的 derivation 信息。

进一步优化

上面的代码只是一个简单的示例,在实际应用中,我们可能需要根据具体情况进行调整。比如,你可能需要处理更复杂的 SQL 语句,例如包含子查询、JOIN 语句等。你可能还需要对提取的 derivation 信息进行进一步处理,例如格式化、简化等。

一些可以考虑的优化方向包括:

  • 处理子查询 : 递归地解析子查询,并将子查询的 derivation 信息合并到主查询中。
  • 处理 JOIN 语句 : 识别 JOIN 条件,并将 JOIN 相关的字段 derivation 信息关联起来。
  • 简化表达式 : 去除表达式中的一些冗余信息,比如多余的括号、空格等。
  • 格式化输出 : 将 derivation 信息格式化成更易读的形式,比如使用缩进、换行等。

常见问题及解答

  1. sqlparse 库是否支持所有类型的 SQL 语法?

    sqlparse 库支持大多数常见的 SQL 语法,但可能无法完全解析所有数据库的特定语法。在使用时,可以参考 sqlparse 的官方文档,了解其支持的语法范围。

  2. 如何处理 SQL 查询中的注释?

    sqlparse 库可以识别 SQL 查询中的注释,并将其作为单独的 token 处理。在提取 derivation 信息时,可以忽略注释 token。

  3. 如何处理 SQL 查询中的别名?

    在提取 derivation 信息时,需要区分字段的实际名称和别名。可以使用 sqlparse 库提供的 get_name()get_real_name() 方法来获取字段的别名和实际名称。

  4. 如何处理 SQL 查询中的函数调用?

    sqlparse 库可以识别 SQL 查询中的函数调用,并将其作为 Function 类型的 token 处理。在提取 derivation 信息时,可以将函数调用作为一个整体进行处理。

  5. 如何处理 SQL 查询中的 CASE WHEN 语句?

    sqlparse 库可以识别 SQL 查询中的 CASE WHEN 语句,并将其作为 Case 类型的 token 处理。在提取 derivation 信息时,可以遍历 CASE WHEN 语句中的每个分支,提取每个分支的条件和结果表达式。