Peewee多表连接别名查询:无法访问别名属性问题排查
我有一个Entries表,需要遍历40000条记录并在表格中展示。由于该表关联了三个外键表(Customer、Farmer、Product),我想在一次查询中获取所有关联数据,避免多次查询带来的性能问题。
我认为最初的查询生成的SQL里出现<Model: Customer>是问题所在。我的模型类如下:
class BaseModel(Model): class Meta: database = database class Customer(BaseModel): name = TextField() class Farmer(BaseModel): name = TextField(null=False) class Product(BaseModel): name = TextField() class Entries(BaseModel): farmer = ForeignKeyField(Farmer, backref='entries') customer = ForeignKeyField(Customer, backref='entries') product = ForeignKeyField(Product, backref='product') other = TextField() class Meta: table_name = 'entries'
我最初尝试的查询语句:
entries = Entries.select( Entries, Farmer.name.alias('farmer_name'), Customer.name.alias('customer_name'), Product.name.alias('product_name') ).join(Farmer, Customer, Product)
它生成的SQL存在语法错误:
SELECT "t1"."id", "t1"."farmer_id", "t1"."customer_id", "t1"."product_id", "t1"."other", "t2"."name" AS "farmer_name", "t3"."name" AS "customer_name", "t4"."name" AS "product_name" FROM "entries" AS "t1" <Model: Customer> "farmer" AS "t2" ON "product" AS "t4"
我也试过用switch方法单独连接,但没成功。
请问能不能在同一个Entries实例中直接获取这些关联数据?还是只能分开执行查询?
更新
当我用下面带dicts()的查询时可以正常拿到数据,但不用dicts()的话会报错AttributeError: 'Entries' object has no attribute 'farmer_name':
entries = Entries.select( Entries, Farmer.name.alias('farmer_name'), Customer.name.alias('customer_name'), Product.name.alias('product_name') ).join(Farmer, on=(Entries.farmer == Farmer.id)) \ .join(Customer, on=(Entries.customer == Customer.id)) \ .join(Product, on=(Entries.product == Product.id))
问题原因
Peewee的Model实例只会自动映射自身表定义的字段,你通过alias查询的额外字段(比如farmer_name)不会被自动绑定到Entries实例的属性上,所以直接访问会触发AttributeError。
可选方案
1. 通过__data__字典访问额外字段
虽然不能直接用属性访问,但可以通过实例的__data__字典获取这些别名字段:
for entry in entries: farmer_name = entry.__data__['farmer_name'] customer_name = entry.__data__['customer_name'] product_name = entry.__data__['product_name'] # 处理数据逻辑
2. 使用预加载(Prefetch)获取完整关联对象
这是更符合Peewee ORM设计的方式,通过prefetch可以批量加载所有关联对象,避免N+1查询,同时直接通过外键属性访问关联字段:
from peewee import prefetch # 预加载关联数据,仅需4次查询(1次查Entries,3次查关联表) entries = prefetch( Entries.select(), Farmer, Customer, Product ) # 遍历访问关联字段 for entry in entries: farmer_name = entry.farmer.name customer_name = entry.customer.name product_name = entry.product.name # 处理数据逻辑
3. 给Entries模型添加属性方法
如果坚持要用别名字段的方式,可以给Entries模型添加属性方法来封装__data__的访问:
class Entries(BaseModel): # 原有字段定义... @property def farmer_name(self): return self.__data__.get('farmer_name') @property def customer_name(self): return self.__data__.get('customer_name') @property def product_name(self): return self.__data__.get('product_name')
之后就可以像访问普通属性一样调用entry.farmer_name,但前提是查询语句必须包含对应的别名字段。
另外,你最初的多表join写法错误,Peewee的join方法默认只能连接一个表,多表连接需要链式调用并指定连接条件(如你更新后的写法),但使用prefetch会更简洁易维护。
内容的提问来源于stack exchange,提问作者lazy_coder

