使用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)
错误原因分析
- 表提取逻辑错误:遍历
parsed_query.tokens时,第一个IdentifierList是SELECT后的字段列表(EMPLOYEE.EMPNO, POSITION),并非FROM子句中的表,导致tables混入字段名,后续处理出错。 - Condition操作数类型不匹配:并非所有
condition.left或condition.right都是Identifier对象,比如SALARY <= 3000中的3000是Token类型,没有get_name()方法;STARTDATE <= ENDDATE的左右操作数也是无表前缀的Token,调用get_name()直接报错。 - 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
相关产品推荐
相关产品推荐

