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
是三种类型集合:
Any
:标量值。例如,int、datetime、str以及SQLAlchemy 模型类实例等。Row
:SQLAlchemy的行对象。Row
父类BaseRow
的__iter__
方法揭示Row
底层数据源于BaseRow
的属性_data: _RawRowType
,其中_RawRowType
是一个Any
元组_RawRowType = Tuple[Any, ...]
,即Row
底层元组每个元素都是Any
类型。RowMapping
:Row
的字典形式,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())等类似,不再逐一列举,整体总结一下,”最终数据格式“有如下形式:
- _RowData,即 Row[Any]、RowMapping、Any
- Optional[_RowData]
- Sequence[_RowData]
- Iterator[_RowData]
- Iterator[Sequence[_RowData]]
由此,若要对”最终数据格式“转换为 Python 基本数据类型,可以由如下方法转换:
- 判断第一层是否为迭代器,是则转列表
- 判断第二层不是 _RowData,且迭代一层
- 处理 _RowData,将 Row 处理为 RowMapping,RowMapping 可以强制转为 dict
- 若 Any 为 模型类对象,调用模型类对象 to_dict 方法,模型类对象 to_dict 方法需要自定义
- 其他标量类型不处理