O(1)Django ORM策略查询相关对象的相关对象

时间:2015-01-08 23:27:25

标签: django

Foo和Bar之间的关系是通过Baz如下:

class Foo(Model):
   # stuff

class Bar(Model)
   # stuff

class Baz(Model):
   foos = ManyToManyField("Foo")
   bar = ForeignKey("Bar")

我基本上需要生成以下代表Bars的dict,这些代码与每个FooBaz(在dict comprehension伪代码中)相关:

{ foo.id: [ 与foo相关的唯一条形图列表 ] for foo in all foos}

我当前可以使用O(N)查询生成我的数据结构(每个Foo有1个查询),但是有很多数据这是一个瓶颈,我需要将它优化为O(1)(本身不是单个查询) ,但无论任何模型的数据大小如何,都有固定数量的查询),同时也最大限度地减少了python中数据的迭代。

2 个答案:

答案 0 :(得分:3)

如果您可以删除SQL,则可以使用单个查询(appname应该为所有表名添加前缀):

select distinct foo.id, bar.id
from baz_foos
join baz on baz_foos.baz_id = baz.id
join foo on baz_foos.foo_id = foo.id
join bar on baz.bar_id = bar.id

baz_foos是Django创建的多对多表。

@Alasdair的解决方案可能/可能更具可读性(尽管如果你出于性能原因这样做可能并不是最重要的)。他的解决方案恰好使用两个查询(这几乎没有区别)。我看到的唯一问题是,如果你有大量的Baz对象,因为生成的sql看起来像这样:

SELECT "foobar_baz"."id", "foobar_baz"."bar_id", "foobar_bar"."id" 
FROM "foobar_baz" 
INNER JOIN "foobar_bar" ON ("foobar_baz"."bar_id" = "foobar_bar"."id")

SELECT
    ("foobar_baz_foos"."baz_id") AS "_prefetch_related_val", 
    "foobar_foo"."id" 
FROM "foobar_foo" 
INNER JOIN "foobar_baz_foos" ON ("foobar_foo"."id" = "foobar_baz_foos"."foo_id") 
WHERE "foobar_baz_foos"."baz_id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 
    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 
    35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 
    55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 
    75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 
    95, 96, 97, 98, 99, 100, 101)

如果你只有几个Bar和几百Foo,我会这样做:

from django.db import connection
from collections import defaultdict

# foos = {f.id: f for f in Foo.objects.all()}
bars = {b.id: b for b in Bar.objects.all()}

c = connection.cursor()
c.execute(sql)  # from above
d = defaultdict(set)
for f_id, b_id in c.fetchall():
    d[f_id].add(bars[b_id])

答案 1 :(得分:2)

使用select_relatedprefetch_related,我认为您可以使用2个查询构建所需的数据结构:

out = {}
bazes = Baz.objects.select_related('bar').prefetch_related('foos')
for baz in bazes:
    for foo in baz.foos.all():
        out.setdefault(foo.id, set()).add(baz.bar)

输出字典的值是设置,而不是问题中的列表,以确保唯一性。