You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用sqlparse优化SQL查询时遇AttributeError: 'Token'无get_name属性

问题描述

需要优化以下SQL查询,移除其中的STARTDATE <= ENDDATE条件:

SELECT EMPLOYEE.EMPNO, POSITION
FROM EMPLOYEE E, JOBHISTORY J
WHERE E. EMPNO = J. EMPNO
AND STARTDATE <= ENDDATE
AND SALARY <= 3000;

目标得到:

SELECT EMPLOYEE.EMPNO, POSITION
FROM EMPLOYEE E, JOBHISTORY J
WHERE E. EMPNO = J. EMPNO
AND SALARY <= 3000;

使用sqlparse库时,报错AttributeError: 'Token' object has no attribute 'get_name',原代码如下:

import sqlparse
import networkx as nx
import matplotlib.pyplot as plt

# example query
query = "SELECT EMPLOYEE.EMPNO, POSITION FROM EMPLOYEE E, JOBHISTORY J WHERE E.EMPNO = J.EMPNO AND STARTDATE <= ENDDATE AND SALARY <= 3000"

# parse the query
parsed_query = sqlparse.parse(query)[0]
select_stmt = parsed_query.tokens[0]

# extract the tables and conditions from the query
tables = []
conditions = []
for token in parsed_query.tokens:
    if isinstance(token, sqlparse.sql.IdentifierList):
        for T in token.get_identifiers():
            tables.append(T.get_name())
    elif isinstance(token, sqlparse.sql.Where):
        for condition in token.tokens:
            if isinstance(condition, sqlparse.sql.Comparison):
                conditions.append(condition)

# remove unnecessary conditions
new_conditions = []
for condition in conditions:
    if "startdate" not in condition.normalized:
        new_conditions.append(condition)
conditions = new_conditions

# generate query tree
G = nx.Graph()
for table in tables:
    G.add_node(table)
for condition in conditions:
    table1 = condition.left.get_name()
    table2 = condition.right.get_name()
    G.add_edge(table1, table2)

# visualize query tree
nx.draw(G, with_labels=True)
plt.show()

# generate optimized query
new_query = select_stmt.to_unicode()
new_query += " FROM " + ", ".join(tables)
new_query += " WHERE " + " AND ".join([str(condition) for condition in conditions])
print(new_query)
错误原因分析
  1. 表提取逻辑错误:遍历parsed_query.tokens时,第一个IdentifierList是SELECT后的字段列表(EMPLOYEE.EMPNO, POSITION),并非FROM子句中的表,导致tables混入字段名,后续处理出错。
  2. Condition操作数类型不匹配:并非所有condition.left或condition.right都是Identifier对象,比如SALARY <= 3000中的3000是Token类型,没有get_name()方法;STARTDATE <= ENDDATE的左右操作数也是无表前缀的Token,调用get_name()直接报错。
  3. SELECT子句提取错误:parsed_query.tokens[0]仅为SELECT关键字,不是完整的SELECT字段部分,导致拼接新查询时内容缺失。
解决后的代码
import sqlparse
import networkx as nx
import matplotlib.pyplot as plt

# 示例查询
query = "SELECT EMPLOYEE.EMPNO, POSITION FROM EMPLOYEE E, JOBHISTORY J WHERE E.EMPNO = J.EMPNO AND STARTDATE <= ENDDATE AND SALARY <= 3000"

# 解析查询
parsed_query = sqlparse.parse(query)[0]

# 提取SELECT子句、FROM子句的表、WHERE子句的条件
select_part = None
tables = []
conditions = []

# 遍历解析后的token,定位各部分
for token in parsed_query.tokens:
    # 提取完整SELECT子句(跳过空白符)
    if isinstance(token, sqlparse.sql.TokenList) and token.tokens[0].normalized == 'SELECT':
        select_part = token.to_unicode().strip()
    # 提取FROM子句中的表:先确认前一个token是FROM
    elif isinstance(token, sqlparse.sql.IdentifierList):
        prev_token = parsed_query.tokens[parsed_query.tokens.index(token)-1]
        if prev_token.normalized == 'FROM':
            for table in token.get_identifiers():
                # 优先取表别名,无别名则取原名
                tables.append(table.get_alias() or table.get_name())
    # 提取WHERE子句中的比较条件
    elif isinstance(token, sqlparse.sql.Where):
        for sub_token in token.tokens:
            if isinstance(sub_token, sqlparse.sql.Comparison):
                conditions.append(sub_token)

# 移除包含startdate的条件(忽略大小写)
filtered_conditions = [cond for cond in conditions if "startdate" not in cond.normalized.lower()]

# 生成查询关联图(仅处理表间关联条件)
G = nx.Graph()
for table in tables:
    G.add_node(table)

for cond in filtered_conditions:
    left = cond.left
    right = cond.right
    left_table = None
    right_table = None
    
    # 提取左操作数的表前缀(仅处理带点的标识符)
    if isinstance(left, sqlparse.sql.Identifier) and '.' in left.normalized:
        left_table = left.normalized.split('.')[0]
    # 提取右操作数的表前缀
    if isinstance(right, sqlparse.sql.Identifier) and '.' in right.normalized:
        right_table = right.normalized.split('.')[0]
    
    # 仅当左右都关联到已提取的表时,添加边
    if left_table and right_table and left_table in tables and right_table in tables:
        G.add_edge(left_table, right_table)

# 可视化查询图
nx.draw(G, with_labels=True, node_size=1500, font_size=12)
plt.show()

# 生成并格式化优化后的SQL
new_query = f"{select_part} FROM {', '.join(tables)} WHERE {' AND '.join([cond.to_unicode().strip() for cond in filtered_conditions])}"
new_query = sqlparse.format(new_query, reindent=True, keyword_case='upper')
print(new_query)
关键改动说明
  • 精准提取表:通过判断IdentifierList的前置token是否为FROM,确保只提取FROM子句中的表,同时支持识别表别名。
  • 兼容多种操作数类型:不再强制调用get_name(),仅对带表前缀的标识符提取表部分,避免非Identifier对象报错;单字段过滤条件不参与图的边生成。
  • 完整提取SELECT子句:通过识别包含SELECT关键字的TokenList,获取完整的字段选择部分,保证拼接后的查询结构完整。
  • 大小写无关的条件过滤:将条件转为小写后判断是否包含startdate,避免大小写差异导致的过滤失效。

内容的提问来源于stack exchange,提问作者sali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 19:00:34