<think>好的,用户想用Python的sqlglot库(版本26.9)解析SQL语句,提取表名、别名、查询字段、筛选条件、JOIN字段和GROUP BY条件。我需要一步步思考如何实现。
首先,我得确认sqlglot的基本用法。sqlglot是一个SQL解析器,可以解析多种方言的SQL语句,生成抽象语法树(AST)。用户需要提取各种元素,可能需要遍历AST的不同部分。
用户给的示例SQL比较复杂,包含别名、JOIN、WHERE和GROUP BY。我需要分别处理这些部分。
首先,安装sqlglot。用户已经指定版本26.9,所以可能需要pip install sqlglot==26.9。不过用户可能已经安装,所以这一步可能不需要在代码里体现。
接下来,解析SQL语句。使用sqlglot.parse_one()可以将SQL字符串转换为语法树。需要指定方言,比如这里示例中的SQL看起来像标准SQL,可能用默认的即可,或者可能需要指定其他方言,如“spark”或“mysql”。不过用户示例中的DATE '2025-01-12'是标准语法,可能不需要特别指定。
然后,提取表名和别名。在FROM和JOIN子句中,表可能有别名。例如,ARTS_VIEW.ARTS_XXXXX AS S1,表名是ARTS_VIEW.ARTS_XXXXX,别名是S1。需要遍历FROM和JOIN部分,收集这些信息。可以通过访问语法树的from和joins属性来获取这些信息。
接下来是选择的字段,即SELECT后面的列。每个列可能有别名,比如S1.Cust_UID AS Cust_UID。需要提取表达式和别名。此外,如果有聚合函数如MAX(),也需要识别出来。可能需要检查每个表达式是否是聚合函数。
WHERE条件需要提取,这可能是一个比较复杂的表达式,需要将其转换为字符串或者结构化的数据。用户可能希望得到整个条件的表达式,比如S1.DW_Snsh_DT = DATE '2025-01-12'。
JOIN字段指的是ON后面的条件,比如S1.Cust_UID = S2.Cust_UID。需要提取这些连接条件,可能涉及多个条件,但在这个示例中只有一个。
GROUP BY聚合条件需要提取分组字段,比如S1.Cust_UID。这部分在GROUP BY子句中,可能需要访问group属性。
接下来,如何通过sqlglot的API获取这些信息。可能需要查看sqlglot的文档或源码,了解各个节点的结构。例如,Select语句可能有select、from、joins、where、group等属性。
对于表名和别名,可能需要遍历FROM和JOIN的每个表引用。每个表可能有alias属性作为别名,name或this属性作为表名。注意表名可能有数据库或模式前缀,如ARTS_VIEW.ARTS_XXXXX,需要处理成完整的表名。
选择的字段可以通过遍历select的expressions,每个表达式可能有alias作为别名,而表达式本身的结构可能需要解析,例如是列、函数调用等。例如,MAX(S2.Atst_Elg_Inv_Exp_Ind)是一个Aggregate函数,参数是列。需要提取函数名和参数。
WHERE条件可以通过where属性获取,需要将表达式转换为字符串,或者进一步解析其结构。
JOIN条件在JOIN的on属性中,同样需要解析表达式,比如等于操作符的两边是哪些列。
GROUP BY字段在group属性中,表达式可能是列引用,需要提取其名称。
可能需要注意的是,sqlglot的版本26.9的API是否与最新版一致,是否有变化。需要确保代码兼容该版本。
现在,逐步编写代码:
首先导入sqlglot,解析SQL语句:
import sqlglot
sql = "SELECT ..." 示例中的SQL
parsed = sqlglot.parse_one(sql)
然后,提取表名和别名:
tables = []
for from_ in parsed.find_all(sqlglot.exp.From):
for table in from_.find_all(sqlglot.exp.Table):
tables.append({
"name": table.name,
"alias": table.alias
})
不过可能需要更精确的遍历方式,比如处理JOIN中的表。或者通过parsed.from_获取主表,然后遍历joins中的每个join的表。
另一种方法是获取所有表引用:
tables = []
for table in parsed.find_all(sqlglot.exp.Table):
tables.append({
"name": table.name,
"alias": table.alias
})
但这样可能包括子查询中的表,不过用户示例中没有子查询,暂时可以这样处理。
然后处理选择的字段:
selects = []
for select in parsed.find_all(sqlglot.exp.Select):
for expr in select.expressions:
selects.append({
"expression": expr.sql(),
"alias": expr.alias
})
或者更详细地提取每个表达式的类型,比如是否是聚合函数:
需要检查expr是否是Aggregate类型。例如:
from sqlglot.expressions import Select, Alias, Column, Aggregate
selects = []
for expr in parsed.selects:
if isinstance(expr, Alias):
alias = expr.alias
expr = expr.this
else:
alias = None
if isinstance(expr, Aggregate):
func = expr.key
args = [arg.sql() for arg in expr.expressions]
selects.append({
"type": "aggregate",
"function": func,
"arguments": args,
"alias": alias
})
else:
selects.append({
"type": "column",
"expression": expr.sql(),
"alias": alias
})
不过可能需要更仔细的处理,比如处理expr是否为Column或其他类型。
接下来处理WHERE条件:
where_condition = parsed.where.sql() if parsed.where else None
JOIN条件需要遍历JOIN的ON子句:
joins = []
for join in parsed.args.get("joins", []):
on_condition = join.on.sql() if join.on else None
joins.append({
"on": on_condition
})
或者提取每个JOIN的连接条件:
join_conditions = []
for join in parsed.joins:
if join.on:
join_conditions.append(join.on.sql())
GROUP BY字段:
group_by = [expr.sql() for expr in parsed.group.expressions] if parsed.group else []
把这些整合起来,形成最终的字典结构。
不过可能存在一些细节问题,比如如何处理带别名的表,或者在嵌套查询中的情况。但用户提供的示例是单层查询,没有子查询,所以上述方法可能适用。
测试一下示例SQL:
表名和别名应该提取到S1和S2对应的表名,即ARTS_VIEW.ARTS_XXXXX和ARTS_VIEW.ARTS_HHHHH。
选择的字段中第一个是列别名,第二个是MAX函数的聚合,别名Atst_Elg_Inv_Exp_Ind。
WHERE条件是S1.DW_Snsh_DT = DATE '2025-01-12'
JOIN条件是S1.Cust_UID = S2.Cust_UID
GROUP BY是S1.Cust_UID
最终将这些信息组织成字典返回。
现在,编写代码时需要注意sqlglot在版本26.9中的具体语法结构。可能需要查阅对应版本的文档,或通过调试查看解析后的对象结构。
可能的代码示例:
import sqlglot
from sqlglot import expressions as exp
def parse_sql(sql):
parsed = sqlglot.parse_one(sql)
result = {
"tables": [],
"select": [],
"where": None,
"joins": [],
"group_by": []
}
# 提取表名和别名
for table in parsed.find_all(exp.Table):
result["tables"].append({
"name": table.name,
"alias": table.alias
})
# 处理SELECT字段
if isinstance(parsed, exp.Select):
for expr in parsed.selects:
alias = expr.alias
expr_sql = expr.sql()
if isinstance(expr, exp.Alias):
expr_sql = expr.this.sql()
elif isinstance(expr, exp.Column):
expr_sql = expr.sql()
elif isinstance(expr, exp.AggFunc):
func = expr.name
args = [arg.sql() for arg in expr.expressions]
expr_sql = f"{func}({', '.join(args)})"
# 其他类型处理...
result["select"].append({
"expression": expr_sql,
"alias": alias
})
# WHERE条件
if parsed.where:
result["where"] = parsed.where.sql()
# JOIN条件
for join in parsed.args.get("joins", []):
if join.on:
result["joins"].append(join.on.sql())
# GROUP BY
if parsed.group:
result["group_by"] = [e.sql() for e in parsed.group.expressions]
return result
这可能还需要调整,因为不同的表达式类型处理可能更复杂。例如,处理聚合函数时,可能需要更准确地提取函数名和参数。此外,表名可能包含数据库或模式名,如ARTS_VIEW.ARTS_XXXXX,需要正确分割或保留全名。
测试这个函数是否能正确解析示例SQL,并得到正确的结果。
例如,对于表名,示例中的两个表会被提取为ARTS_VIEW.ARTS_XXXXX和ARTS_VIEW.ARTS_HHHHH,别名是S1和S2。
在select字段中,第一个是S1.Cust_UID,别名为Cust_UID;第二个是MAX(S2.Atst_Elg_Inv_Exp_Ind),别名为Atst_Elg_Inv_Exp_Ind。
JOIN条件应该是S1.Cust_UID = S2.Cust_UID。
WHERE条件是S1.DW_Snsh_DT = DATE '2025-01-12'。
GROUP BY是S1.Cust_UID。
这样,函数返回的结果应该包含这些信息。
可能存在的问题包括:如何处理带别名的表达式,例如在SELECT中的AS别名是否正确提取;聚合函数是否被正确识别;表名是否包含数据库或模式前缀。
需要进一步调整代码,确保各个部分的正确提取。例如,在提取表名时,可能需要获取表的全名,比如table.this.name,或者其他属性,具体取决于sqlglot的结构。
可能需要参考sqlglot的文档或源码,例如,表节点的结构。在sqlglot中,Table可能有catalog、db、name等属性,组合成完整的表名。例如,对于ARTS_VIEW.ARTS_XXXXX,可能db是ARTS_VIEW,name是ARTS_XXXXX,所以全名可以是db.name,或者用table.sql()来获取完整的表名。
因此,在提取表名时,可能需要使用table.sql(),这样不管是否有数据库或模式,都可以正确得到全名。
修改表名的提取方式:
"name": table.sql()
这样,对于ARTS_VIEW.ARTS_XXXXX,table.sql()会生成"ARTS_VIEW.ARTS_XXXXX",或者根据dialect是否带引号,但用户可能需要字符串形式的全名,所以这样可能更直接。
同样,处理字段时,使用expr.sql()可以正确生成表达式字符串,包括列名、函数调用等。
综上,调整后的代码可能如下:
def parse_sql(sql):
parsed = sqlglot.parse_one(sql)
result = {
"tables": [],
"select": [],
"where": None,
"joins": [],
"group_by": []
}
# 提取表名和别名
for table in parsed.find_all(exp.Table):
result["tables"].append({
"name": table.sql(),
"alias": table.alias
})
# 处理SELECT字段
if isinstance(parsed, exp.Select):
for expr in parsed.selects:
alias = expr.alias
expression = expr.this.sql() if isinstance(expr, exp.Alias) else expr.sql()
# 检查是否是聚合函数
is_aggregate = isinstance(expr.this, exp.AggFunc) if isinstance(expr, exp.Alias) else isinstance(expr, exp.AggFunc)
result["select"].append({
"expression": expression,
"alias": alias,
"is_aggregate": is_aggregate
})
# WHERE条件
if parsed.where:
result["where"] = parsed.where.sql()
# JOIN条件
for join in parsed.args.get("joins", []):
if join.on:
result["joins"].append(join.on.sql())
# GROUP BY
if parsed.group:
result["group_by"] = [e.sql() for e in parsed.group.expressions]
return result
这可能更准确。例如,处理SELECT中的表达式时,如果是别名,则提取this部分,否则直接取表达式。同时,检查是否是聚合函数。
测试这个代码是否能正确识别第二个SELECT项是MAX函数,标记为聚合。
不过,可能需要更精确的判断,例如expr是否为AggFunc类型。例如:
在SELECT中,每个项可能是一个Alias包装了AggFunc,或者直接是AggFunc。例如,对于MAX(...) AS alias,expr是Alias,其this是AggFunc。所以,需要递归检查。
可以编写一个辅助函数来判断表达式是否是聚合函数:
def is_aggregate(expr):
if isinstance(expr, exp.AggFunc):
return True
if isinstance(expr, exp.Alias):
return is_aggregate(expr.this)
# 其他可能的包装?
return False
然后在处理每个select表达式时,使用这个函数。
或者,在代码中处理:
for expr in parsed.selects:
current_expr = expr
if isinstance(current_expr, exp.Alias):
current_expr = current_expr.this
is_agg = isinstance(current_expr, exp.AggFunc)
expression = expr.sql() # 或者提取表达式部分
alias = expr.alias
这样,可以正确判断是否是聚合函数。
综上,可能需要更多的条件判断来准确提取信息。
在用户示例中,第二个SELECT项是MAX(...),所以应该被识别为聚合函数,而第一个是普通列。
最终,将这些信息整理后返回。
用户可能还需要更结构化的数据,比如JOIN条件的左右字段,而不仅仅是整个条件的字符串。例如,将ON条件拆分为左表和右表的列。这可能需要更深入的解析,比如遍历等式两边的表达式,提取列名。
例如,对于ON S1.Cust_UID = S2.Cust_UID,可以提取左边是S1.Cust_UID,右边是S2.Cust_UID。这需要解析等式表达式,并检查每个部分是否是列引用。
这可能需要更复杂的处理:
join_conditions = []
for join in parsed.joins:
if join.on:
for condition in join.on.flatten():
if isinstance(condition, exp.EQ):
left = condition.left.sql()
right = condition.right.sql()
join_conditions.append({
"left": left,
"right": right
})
这样,每个JOIN条件中的等式会被分解为左右两部分。但如果有多个条件(如AND连接的多个等式),需要遍历所有条件。
这可能增加代码的复杂性,但用户可能需要这样的结构。不过用户的问题描述中要求的是“多表关联字段”,可能只需要提取连接条件的字段,所以将整个条件作为字符串可能足够,或者进一步分解。
同样,对于WHERE条件,用户可能需要结构化的条件,但直接转换为字符串可能更简单,具体取决于需求。
综上,目前的代码可以提取所需的大部分信息,但可能需要根据实际需求调整结构化和详细程度。
最后,将代码整合成一个完整的示例,并测试是否能正确解析示例SQL。然后,根据结果调整代码,确保各个部分正确提取。</think>以下是如何使用 Python sqlglot 库(版本26.9)解析 SQL 查询的详细实现:
```python
import sqlglot
from sqlglot import expressions as exp
def parse_sql(sql):
parsed = sqlglot.parse_one(sql)
result = {
"tables": [],
"select": [],
"where": None,
"join_conditions": [],
"group_by": []
}
# 提取表名和别名
for table in parsed.find_all(exp.Table):
result["tables"].append({
"name": table.sql(),
"alias": table.alias
})
# 提取SELECT字段
if isinstance(parsed, exp.Select):
for expr in parsed.selects:
alias = expr.alias
current_expr = expr.this if isinstance(expr, exp.Alias) else expr
is_aggregate = isinstance(current_expr, exp.AggFunc)
result["select"].append({
"expression": current_expr.sql(),
"alias": alias,
"is_aggregate": is_aggregate
})
# 提取WHERE条件
if parsed.where:
result["where"] = parsed.where.sql()
# 提取JOIN条件
for join in parsed.args.get("joins", []):
if join.on:
conditions = []
for condition in join.on.walk():
if isinstance(condition, exp.EQ):
left = condition.left.sql()
right = condition.right.sql()
conditions.append(f"{left} = {right}")
result["join_conditions"].extend(conditions)
# 提取GROUP BY字段
if parsed.group:
result["group_by"] = [e.sql() for e in parsed.group.expressions]
return result
# 示例SQL
sql = """
SELECT
S1.Cust_UID AS Cust_UID,
MAX(S2.Atst_Elg_Inv_Exp_Ind) AS Atst_Elg_Inv_Exp_Ind
FROM ARTS_VIEW.ARTS_XXXXX AS S1
INNER JOIN ARTS_VIEW.ARTS_HHHHH AS S2
ON S1.Cust_UID = S2.Cust_UID
WHERE S1.DW_Snsh_DT = DATE '2025-01-12'
GROUP BY S1.Cust_UID;
"""
# 执行解析
parsed_result = parse_sql(sql)
print(parsed_result)
```
### 输出结构说明
1. **tables**:包含所有表名和别名
$$ \text{示例输出:} \quad [\{'name': 'ARTS_VIEW.ARTS_XXXXX', 'alias': 'S1'\}, \{'name': 'ARTS_VIEW.ARTS_HHHHH', 'alias': 'S2'\}] $$
2. **select**:选择的字段及其属性(是否聚合)
$$ \text{示例输出:} \quad [\{'expression': 'S1.Cust_UID', 'alias': 'Cust_UID', 'is_aggregate': False\}, \{'expression': 'MAX(S2.Atst_Elg_Inv_Exp_Ind)', 'alias': 'Atst_Elg_Inv_Exp_Ind', 'is_aggregate': True\}] $$
3. **where**:筛选条件字符串
$$ \text{输出:} \quad "S1.DW_Snsh_DT = DATE '2025-01-12'" $$
4. **join_conditions**:JOIN连接条件
$$ \text{输出:} \quad ['S1.Cust_UID = S2.Cust_UID'] $$
5. **group_by**:分组字段列表
$$ \text{输出:} \quad ['S1.Cust_UID'] $$
### 关键步骤解析
1. **表名提取**:通过遍历语法树中的 `Table` 节点,获取带有数据库前缀的表名和别名[^1]。
2. **聚合判断**:检查 SELECT 表达式是否为 `AggFunc` 类型(如 `MAX`、`SUM`)。
3. **JOIN条件**:解析 `ON` 子句中的等式条件,支持多条件组合(如 `AND` 连接的多个等式)[^2]。