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

O(1) Django ORM strategy to query related objects of related objects

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

class Foo(Model):
   # stuff

class Bar(Model)
   # stuff

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

我基本上需要生成以下字典,表示与每个 FooBaz 相关的 Bars(在字典理解伪代码中):

{ foo.id: [通过任何 baz 与 foo 相关的唯一柱列表] for foo in all foos}

我目前可以使用 O(N) 查询(每个 Foo 1 个查询)生成我的数据结构,但是对于大量数据这是一个瓶颈,我需要将其优化为 O(1)(不是单个查询本身,但无论任何模型的数据大小如何,查询的数量都是固定的),同时还最大限度地减少了 python.

中数据的迭代

如果你可以下降到 SQL,你可以使用单一查询(appname 应该作为所有 table 名称的前缀):

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 创建的多对多 table。

@Alasdair 的解决方案 possibly/probably 更具可读性(尽管如果您出于性能原因这样做可能不是最重要的)。他的解决方案恰好使用了两个查询(几乎没有区别)。我看到的唯一问题是如果您有大量 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])

使用 select_related and prefetch_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)

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