Django 嵌套 QuerySets

5 投票
2 回答
8394 浏览
提问于 2025-04-16 11:00

我有一个Django的数据模型,像这样(数据字段省略):

class Atom(Model):
    pass

class State(Model):
    atom = ForeignKey(Atom)

class Transition(Model):
    atom = ForeignKey(Atom)
    upstate = ForeignKey(State,related_name='uptrans')
    lostate = ForeignKey(State,related_name='lotrans')

当我查询时,想要限制的字段可能在任一模型中,所以最简单的方法是用 Transition.objects.filter(...) 来查询,因为可以通过外键访问其他模型中的所有字段。我们把这个查询结果叫做 t

现在,我还想要一个与 t 对应的 Atom 模型的查询集 a,可以通过 a = t.values('atom').distinct() 来实现。到这里为止都没问题。

不过,我还希望 a 中的每个条目都有一个属性/字段,用来保存这个 Atom 的 States 的查询集,并且这个查询集要反映原始选择 t 的条件,可以通过 upstatelostate 外键来实现。

到目前为止,我是通过遍历 t 来创建 States 的查询集,先把 values('upstate_id')values('lostate_id') 加入一个 Python 的 set() 中,以去掉重复项,然后用这个列表查询 States。但是这样我无法在 Atoms 中实现 States 的嵌套结构。

如果有任何建议,欢迎分享,最好是能使用未计算的 QuerySet,因为我不是把它们传入模板,而是传给一个生成器(yield 语句),这是一种处理大量数据的好方法。

2 个回答

1

很高兴你对set有一定的了解。不过,使用SQL中的In并不会让你的数据重复。我们来想一想这个问题。如果我说:“给我这个列表中的一个元素:(1, 2, 3, 3, 3, 4)”,数据库会返回1、2、3和4。为了简单起见,我不会让Python来处理set的运算,因为数据库本身就能很好地处理这个问题。虽然有时候需要用到set,但在你的情况下似乎并不需要。

给你一个替代方案:

states = State.objects.filter(
    Q(pk__in=t.values_list('upstate', flat=True)) |
    Q(pk__in=t.values_list('lostate', flat=True)
)

即便如此,似乎你的模型需要一些调整,但我不太明白你想要达到什么效果。注意,在我的替代方案中,我并没有处理元素。我使用Q对象来执行“或”操作,但你也可以在State模型中添加一个标志,表示它是高还是低。或者你可以使用一个通过表的多对多关系。还有,为什么你的转换和状态都要和一个元素关联呢?你可以直接从Transition中去掉atom,然后像这样从State中获取atom

atoms = Atom.objects.filter(
    pk__in=State.objects.filter(
        Q(pk__in=t.values_list('upstate', flat=True)) |
        Q(pk__in=t.values_list('lostate', flat=True)
    ).values_list('atom', flat=True)
)
2

我觉得下面这个函数可以实现我上面说的功能,但我不太确定用原始查询集再通过原子进行进一步过滤的这个循环是不是正确的方法。

def getAtomsWithStates(t):
    atom_ids = set( t.values_list('atom_id',flat=True) )
    atoms = Atoms.objects.filter(pk__in=atom_ids)
    for atom in atoms:
        upstate_ids = t.filter(atom=atom).values_list('upstate_id',flat=True)
        lostate_ids = t.filter(atom=atom).values_list('lostate_id',flat=True)
        all_ids = set( upstate_ids + lostate_ids )

        # attach the new QuerySet to the entry in the outer one:
        atom.States = State.objects.filter(pk__in=all_ids)

    return atoms

现在我可以像这样进行我需要的嵌套循环:

someAtoms = getAtomsWithStates( Transition.objects.filter(...) )
for atom in someAtoms:
    for state in atom.States:
        print state.field

不过,再说一次,可能还有更聪明的解决方案,我肯定会感兴趣的。

撰写回答