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

pyodbc cursor.fetchall()返回嵌套元组结构问题求助

问题

我写了一个处理数据库操作的DBConnector类,其中_run_query方法可执行select、insert等各类查询,但执行select查询时,返回的结果格式不符合预期。我想要的是像["New", "Ongoing", "Closed", "Canceled", "Solved"]这样的普通列表,实际得到的却是[("New",), ("Ongoing",), ("Closed",), ("Canceled",), ("Solved",)]这种嵌套元组的列表。

相关代码如下:

database.connector.py

import logging
import pyodbc

class DBConnector:
   ...

    def _open_cnxn(self) -> pyodbc.Connection:
        """Opens a connection to the database.

        Returns:
            pyodbc.Connection: Connection object
        """

        self.logger.info(f'Openning connection to "{self.server}".')
        return pyodbc.connect(self.conn_string)

    def _run_query(self, query: str, type: str = "select") -> list:
        """Run the given query

        Args:
            query (str): query string
            type (str, optional): select, insert, update, delete, create. Defaults to "select".

        Returns:
            list: Query result if select | else commits and returns [None]
        """

        try:
            cursor = self._open_cnxn().cursor()
            self.logger.info(f'Executing query: "{query}".')
            cursor.execute(query)
        except Exception as error:
            self.logger.error(
                f'There was an error while trying to execute the query.: "{str(error)}"'
            )

        # DEBUG
        columns = [column[0] for column in cursor.description]
        results = [row for row in cursor.fetchall()]

        print(columns)
        print(results)
        # DEBUG

        match type:
            case "select":
                return results
            case "insert" | "update" | "delete" | "create":
                cursor.commit()
                self.logger.info(f"Commited changes to {columns}")
                return [None]

连接字符串(定义在__init__()中)

self.conn_string = f"DRIVER={{ODBC Driver 17 for SQL Server}}; \
                             SERVER={self.server}; \
                             DATABASE={self.database}; \
                             UID={self.username}; \
                             PWD={self.password}"

测试模块testing.py

import configparser
import logging
import sys

from database.connector import DBConnector

secrets = configparser.ConfigParser()
secrets.read("secrets/config.ini")

SERVER = secrets.get("DB_HOMOLOG", "server")
DATABASE = secrets.get("DB_HOMOLOG", "database")
USERNAME = secrets.get("DB_HOMOLOG", "username")
PASSWORD = secrets.get("DB_HOMOLOG", "password")

connector = DBConnector(
    server=SERVER, database=DATABASE, username=USERNAME, password=PASSWORD
)

date_field = "STATUS"
print(connector._run_query("SELECT STATUS FROM TICKETS GROUP BY STATUS"))
原因分析

这是pyodbc的默认行为:cursor.fetchall()返回的每一行结果都是一个元组,哪怕只查询了单个字段。每个元组对应数据库中的一行,元组内的元素就是该行的各个字段值。你查询的是单个字段STATUS,所以每一行就是仅含一个元素的元组,最终结果自然是元组组成的列表。

解决思路

方案1:针对单个字段查询转换格式

直接修改_run_query方法中获取结果的代码,提取每个元组的第一个元素:

# 替换原results赋值行
results = [row[0] for row in cursor.fetchall()]

这样返回的就是单个值组成的普通列表,完全符合预期。

方案2:兼容单字段与多字段查询

如果方法需要同时支持单字段和多字段查询,可以通过判断字段数量动态调整返回格式:

columns = [column[0] for column in cursor.description]
rows = cursor.fetchall()

if len(columns) == 1:
    # 单字段查询,返回普通列表
    results = [row[0] for row in rows]
else:
    # 多字段查询,可选择返回元组列表或更易读的字典列表
    # 字典列表示例:results = [dict(zip(columns, row)) for row in rows]
    results = rows

若选择返回字典列表,结果会变成[{"STATUS": "New"}, {"STATUS": "Ongoing"}, ...],可读性更强,适合多字段场景。

额外优化建议

你的代码中_open_cnxn()每次创建新连接后未关闭,容易造成资源泄漏,建议在try...finally块中管理连接和游标:

def _run_query(self, query: str, type: str = "select") -> list:
    cnxn = None
    cursor = None
    try:
        cnxn = self._open_cnxn()
        cursor = cnxn.cursor()
        self.logger.info(f'Executing query: "{query}".')
        cursor.execute(query)
        
        columns = [column[0] for column in cursor.description]
        rows = cursor.fetchall()
        
        # 结果格式处理逻辑...
        
    except Exception as error:
        self.logger.error(f'There was an error while trying to execute the query.: "{str(error)}"')
        raise  # 重新抛出异常让上层处理
    finally:
        if cursor:
            cursor.close()
        if cnxn:
            cnxn.close()
    
    # 后续返回逻辑...

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 19:20:10