假设我的模型Box
的{{1}}指向GenericForeignKey
实例或Apple
实例。反过来,Chocolate
和Apple
分别将ForeignKeys设为Chocolate
和Farm
。我想显示Factory
es列表,我需要访问Box
和Farm
。如何在尽可能少的数据库查询中执行此操作?
最小的说明性示例:
Factory
以下是我尝试过的一些事情。在所有这些示例中, N 是Box的数量。查询计数假定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)
和ContentType
的{{1}}已经被缓存,因此Apple
调用不会访问数据库。
1)天真:
Chocolate
1 (获取框)+ N (每个Box获取Apple或巧克力)+ N (每个Apple获取一个Farm)和每个巧克力的工厂查询。
2)get_for_model()
在这里没有帮助,因为print [box for box in Box.objects.all()]
是select_related
。
3)从django 1.4开始,Box.content_object
可以获取GenericForeignKey
s。
prefetch_related
1 (获取框)+ 2 (为所有框提取苹果和巧克力)+ N (每个Apple获取一个农场)和每个巧克力的工厂查询。
4)显然GenericForeignKey
不够智能,无法关注GenericForeignKeys的ForeignKeys。如果我尝试:
print [box for box in Box.objects.prefetch_related('content_object').all()]
理所当然地抱怨prefetch_related
个对象没有print [box for box in Box.objects.prefetch_related(
'content_object__farm',
'content_object__factory').all()]
字段,反之亦然。
5)我能做到:
Chocolate
1 (获取框)+ 2 (为所有框提取苹果和巧克力)+ 2 (为所有苹果提取农场)所有巧克力的工厂和工厂查询。缺点是我必须手动合并和排序两个查询集(farm
,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')
)。在我的实际应用程序中,我将在分页的ModelAdmin中显示这些Box。如何在此处集成此解决方案并不明显。也许我可以写一个自定义Paginator来透明地进行缓存?
6)我可以根据this拼凑一些也可以进行O(1)查询的东西。但是如果我能避免的话,我宁愿不要弄乱内部(boxes_with_apples
)。
总结:打印Box需要访问GenericForeignKey的ForeignKeys。如何在O(1)查询中打印N个框?(5)我能做的最好,还是有更简单的解决方案?
奖励积分:您如何重构此数据库架构以简化此类查询?
答案 0 :(得分:10)
您可以手动实现类似prefetch_selected
的内容并使用Django的select_related
方法,这将使数据库查询中的连接成为可能。
apple_ctype = ContentType.objects.get_for_model(Apple)
chocolate_ctype = ContentType.objects.get_for_model(Chocolate)
boxes = Box.objects.all()
content_objects = {}
# apples
content_objects[apple_ctype.id] = Apple.objects.select_related(
'farm').in_bulk(
[b.object_id for b in boxes if b.content_type == apple_ctype]
)
# chocolates
content_objects[chocolate_ctype.id] = Chocolate.objects.select_related(
'factory').in_bulk(
[b.object_id for b in boxes if b.content_type == chocolate_ctype]
)
这应该只有3个查询(get_for_model
个查询被省略)。 in_bulk
方法以{id:model}格式返回一个dict。因此,要获取content_object,您需要一个代码:
content_obj = content_objects[box.content_type_id][box.object_id]
但是,我不确定此代码是否会比 O(5)解决方案更快,因为它需要对box queryset进行额外迭代,并且还会生成带WHERE id IN (...)
语句的查询< / p>
但是,如果您仅使用Box模型中的字段对框进行排序,则可以在分页后填充content_objects
dict。但是你需要以某种方式将content_objects
传递给__unicode__
您如何重构此数据库架构以简化此类查询?
我们有类似的结构。我们将content_object
存储在Box
中,但我们使用object_id
和content_object
中的ForeignKey(Box)
代替Apple
和Chocolate
。在Box
中,我们使用get_object
方法返回Apple或巧克力模型。在这种情况下,我们可以使用select_related
,但在大多数用例中,我们按content_type过滤Boxes。所以我们遇到了像你的第五个选择一样的问题。但是,当没有prefetch_selected时,我们开始在Django 1.2上进行项目。
如果您将场/工厂重命名为某个通用名称(如创建者),是否会预取相关的工作?
关于选项6
我可以说任何反对填充_content_object_cache
的内容。
如果您不喜欢处理内部问题,可以填写自定义属性,然后使用
apple = getattr(self, 'my_custop_prop', None)
if apple is None:
apple = self.content_object