SQLAlchemy 查询结果格式分析

SQLAlchemy 中结果集 Result 及其子类定义在 sqlalchemy\engine\result.py 中,故分析 Result 相关数据获取方法(例如,Result.all()、Result.one_or_none())返回的”最终数据格式“从此开始,在SQLALchemy 中,这个”最终数据格式“由_RowData所代表的”行“构成。

_RowData = Union[Row[Any], RowMapping, Any]

A generic form of “row” that accommodates for the different kinds of “rows” that different result objects return, including row, row mapping, and scalar values.
一种通用的“行”形式,适用于不同结果对象返回的不同类型的“行”,包括行、行映射和标量值。

_RowData 是三种类型集合:

  1. Any:标量值。例如,int、datetime、str以及SQLAlchemy 模型类实例等。
  2. Row:SQLAlchemy的行对象。Row父类BaseRow__iter__方法揭示Row底层数据源于BaseRow的属性_data: _RawRowType,其中_RawRowType是一个Any元组_RawRowType = Tuple[Any, ...],即Row底层元组每个元素都是Any类型。
  3. RowMappingRow的字典形式,Row可转换为RowMapping

通过如下代码分析一个具体返回形式:

import asyncio

from sqlalchemy import Column, Integer, String, select
from sqlalchemy import ForeignKey
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.ext.asyncio import async_sessionmaker
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import relationship

Base = declarative_base()


class Group(Base):
    __tablename__ = 'tb_group'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    # 添加与 Item 的一对多关系
    items = relationship("Item", back_populates="group")


class Item(Base):
    __tablename__ = 'tb_item'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    # 添加与 Group 的多对一关系
    group_id = Column(Integer, ForeignKey('tb_group.id'), nullable=False)
    group = relationship("Group", back_populates="items")
    

# 创建异步引擎
engine = create_async_engine(
    "mysql+aiomysql://root:mYsql123456_@localhost/test4",
    echo='debug',
    echo_pool='debug',
)

# 创建会话
async_session = async_sessionmaker(
    engine, expire_on_commit=False, class_=AsyncSession
)


async def prepare():
    # 创建表(如果不存在)
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

    # 插入示例数据
    async with async_session() as session:
        async with session.begin():
            session.add(Group(id=1, name="Group 1"))
            session.add(Item(id=1, name="Item 1", group_id=1))
            session.add(Item(id=2, name="Item 2", group_id=1))
            session.add(Item(id=3, name="Item 3", group_id=1))
            session.add(Group(id=2, name="Group 2"))
            session.add(Item(id=4, name="Item 4", group_id=2))
            session.add(Item(id=5, name="Item 5", group_id=2))
            session.add(Item(id=6, name="Item 6", group_id=2))


async def async_main():
    # await prepare()

    async with async_session() as session:
        stmt = select(Group.id, Group.name).where(Group.id.in_([1, 2]))
        result = await session.execute(stmt)
        data = result.all()  # 返回 Sequence[Row[_TP]],_TP = TypeVar("_TP", bound=Tuple[Any, ...])
        print(type(data[0]))
        print(data[0])
        print(type(data[0]._mapping))
        print(data[0]._mapping)

        # 2025-04-23 23:56:37,686 INFO sqlalchemy.engine.Engine SELECT tb_group.id, tb_group.name
        # FROM tb_group
        # WHERE tb_group.id IN (%s, %s)
        # 2025-04-23 23:56:37,686 INFO sqlalchemy.engine.Engine [generated in 0.00045s] (1, 2)
        # 2025-04-23 23:56:37,687 DEBUG sqlalchemy.engine.Engine Col ('id', 'name')
        # 2025-04-23 23:56:37,689 DEBUG sqlalchemy.engine.Engine Row (1, 'Group 1')
        # 2025-04-23 23:56:37,689 DEBUG sqlalchemy.engine.Engine Row (2, 'Group 2')
        # <class 'sqlalchemy.engine.row.Row'>
        # (1, 'Group 1')
        # <class 'sqlalchemy.engine.row.RowMapping'>
        # {'id': 1, 'name': 'Group 1'}

        # 从以上日志可以看出:Row 是一个元组,元组中存储 select 方法选定的数据,此处选定 Group.id, Group.name,故每个元组中仅包含2个元素,RowMapping 会根据列名作为字典的 key

        stmt = select(Group).where(Group.id.in_([1, 2]))
        result = await session.execute(stmt)
        data = result.all()  # 返回 Sequence[Row[_TP]],_TP = TypeVar("_TP", bound=Tuple[Any, ...])
        print(type(data[0]))
        print(data[0])
        print(type(data[0]._mapping))
        print(data[0]._mapping)

        # 特别的,异步关联有问题
        # print(data[0][0].items)

        # 2025-04-23 23:56:37,691 INFO sqlalchemy.engine.Engine SELECT tb_group.id, tb_group.name
        # FROM tb_group
        # WHERE tb_group.id IN (%s, %s)
        # 2025-04-23 23:56:37,691 INFO sqlalchemy.engine.Engine [generated in 0.00037s] (1, 2)
        # 2025-04-23 23:56:37,692 DEBUG sqlalchemy.engine.Engine Col ('id', 'name')
        # 2025-04-23 23:56:37,692 DEBUG sqlalchemy.engine.Engine Row (1, 'Group 1')
        # 2025-04-23 23:56:37,692 DEBUG sqlalchemy.engine.Engine Row (2, 'Group 2')
        # <class 'sqlalchemy.engine.row.Row'>
        # (<__main__.Group object at 0x0000029B66A25B70>,)
        # <class 'sqlalchemy.engine.row.RowMapping'>
        # {'Group': <__main__.Group object at 0x0000029B66A25B70>}

        # 从以上日志可以看出:
        # 1. Row 是一个元组,元组中存储 select 方法选定的数据,此处选定 Group,故每个元组中仅包含1个元素,RowMapping 会根据模型类名作为字典的 key

        stmt = select(Group.id, Group.name, Group).where(Group.id.in_([1, 2]))
        result = await session.execute(stmt)
        data = result.all()  # 返回 Sequence[Row[_TP]],_TP = TypeVar("_TP", bound=Tuple[Any, ...])
        print(type(data[0]))
        print(data[0])
        print(type(data[0]._mapping))
        print(data[0]._mapping)

        # 2025-04-23 23:56:37,694 INFO sqlalchemy.engine.Engine SELECT tb_group.id, tb_group.name
        # FROM tb_group
        # WHERE tb_group.id IN (%s, %s)
        # 2025-04-23 23:56:37,694 INFO sqlalchemy.engine.Engine [generated in 0.00037s] (1, 2)
        # 2025-04-23 23:56:37,695 DEBUG sqlalchemy.engine.Engine Col ('id', 'name')
        # 2025-04-23 23:56:37,695 DEBUG sqlalchemy.engine.Engine Row (1, 'Group 1')
        # 2025-04-23 23:56:37,695 DEBUG sqlalchemy.engine.Engine Row (2, 'Group 2')
        # <class 'sqlalchemy.engine.row.Row'>
        # (1, 'Group 1', <__main__.Group object at 0x0000029B66A25B70>)
        # <class 'sqlalchemy.engine.row.RowMapping'>
        # {'id': 1, 'name': 'Group 1', 'Group': <__main__.Group object at 0x0000029B66A25B70>}

        # 从以上日志可以看出:Row 是一个元组,元组中存储 select 方法选定的数据,此处选定 Group.id, Group.name, Group,故每个元组中包含3个元素,RowMapping 会根据列名或者模型类名作为字典的 key
        # ORM 支持字段和模型混合查询,查询时 SQL 语句还是按照字段查询,模型由 SQLAlchemy 组装,不过这里的问题就是,当一个表连接语句使得A和B连接,此时希望获取 A 的所有字段和B的某些字段,A 模型和B字段在select中混合使用,就会导致 A 的字段和B的字段不是在同一个层级,这时有一个小技巧,将查模型变为查模型所有列 stmt = select(Group.__table__.columns, Item.id).where(Group.id.in_([1, 2]))


asyncio.run(async_main())

以上通过Result.all()方法展示_RowData具体形式以及”最终数据格式“的部分形式,Result及其子类中其他数据获取方法(例如,Result.all()、Result.one_or_none())等类似,不再逐一列举,整体总结一下,”最终数据格式“有如下形式:

  1. _RowData,即 Row[Any]、RowMapping、Any
  2. Optional[_RowData]
  3. Sequence[_RowData]
  4. Iterator[_RowData]
  5. Iterator[Sequence[_RowData]]

由此,若要对”最终数据格式“转换为 Python 基本数据类型,可以由如下方法转换:

  1. 判断第一层是否为迭代器,是则转列表
  2. 判断第二层不是 _RowData,且迭代一层
  3. 处理 _RowData,将 Row 处理为 RowMapping,RowMapping 可以强制转为 dict
  4. 若 Any 为 模型类对象,调用模型类对象 to_dict 方法,模型类对象 to_dict 方法需要自定义
  5. 其他标量类型不处理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值