假设我有一个模型Box
与GenericForeignKey
指向任何一个Apple
实例或Chocolate
实例。 Apple
和Chocolate
,反过来,有ForeignKeys到Farm
和Factory
分别。 我想显示的列表Box
ES,为此我需要访问Farm
和Factory
。 如何做到这一点在尽可能少的数据库查询越好?
最小的说明性示例:
class Farm(Model):
...
class Apple(Model):
farm = ForeignKey(Farm)
...
class Factory(Model):
...
class Chocolate(Model):
factory = ForeignKey(Factory)
...
class Box(Model)
content_type = ForeignKey(ContentType)
object_id = PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
...
def __unicode__(self):
if self.content_type == ContentType.objects.get_for_model(Apple):
apple = self.content_object
return "Apple {} from Farm {}".format(apple, apple.farm)
elif self.content_type == ContentType.objects.get_for_model(Chocolate):
chocolate = self.content_object
return "Chocolate {} from Factory {}".format(chocolate, chocolate.factory)
下面是我试了几件事情。 在所有这些例子中,N是盒的数目。 查询计数假定ContentType
S代表Apple
和Chocolate
已经被缓存,所以get_for_model()
调用不会打DB。
1)朴素:
print [box for box in Box.objects.all()]
但这1(取盒)+ N(取苹果或巧克力每盒)+ N(取农场每个苹果和工厂每个巧克力)查询。
2) select_related
没有帮助在这里,因为Box.content_object
是GenericForeignKey
。
3)1.4的django的, prefetch_related
可以取GenericForeignKey
秒。
print [box for box in Box.objects.prefetch_related('content_object').all()]
但这1(取盒)+ 2(取所有箱苹果和巧克力)+ N(取农场每个苹果和工厂每个巧克力)查询。
4)显然prefetch_related
是不够聪明遵循GenericForeignKeys的ForeignKeys。 如果我尝试:
print [box for box in Box.objects.prefetch_related( 'content_object__farm', 'content_object__factory').all()]
它理所当然地抱怨说, Chocolate
对象没有一个farm
场,反之亦然。
5)我可以这样做:
apple_ctype = ContentType.objects.get_for_model(Apple)
chocolate_ctype = ContentType.objects.get_for_model(Chocolate)
boxes_with_apples = Box.objects.filter(content_type=apple_ctype).prefetch_related('content_object__farm')
boxes_with_chocolates = Box.objects.filter(content_type=chocolate_ctype).prefetch_related('content_object__factory')
这并不1(取盒)+ 2(取为所有盒苹果和巧克力)+ 2(取农场所有苹果和工厂的所有巧克力)查询。 缺点是,我有合并和排序两个查询集( boxes_with_apples
, boxes_with_chocolates
手动)。 在我的实际应用中,我在一个分页的ModelAdmin显示这些箱子。 这不是明显的如何这个解决方案有整合。 也许我可以写一个自定义分页程序透明地做到这一点缓存?
6)我可以凑齐基于什么这个那个也做O(1)查询。 但我宁愿不具有内部部件(乱_content_object_cache
)如果我能避免它。
总结: 打印盒需要访问GenericForeignKey的ForeignKeys。 我怎样才能打印传单N盒在O(1)查询? 是(5)最好我能做到的,或者是有一个简单的解决方案?
加分点: 你会如何重构这个数据库架构做出这样的查询更容易吗?