web-dev-qa-db-ja.com

Djangoすべての子に対する自己再帰的な外部キーフィルタークエリ

自己参照外部キー関係を持つこのモデルがあります。

class Person(TimeStampedModel):
    name = models.CharField(max_length=32)
    parent = models.ForeignKey('self', null=True, blank=True, related_name='children')

今、私は人のためにすべてのマルチレベルの子供を取得したいと思います。 Djangoクエリを作成するにはどうすればよいですか?再帰関数のように動作する必要があります。

25
Ahsan

モデルにはいつでも再帰関数を追加できます。

編集:SeomGiHanに従って修正

def get_all_children(self, include_self=True):
    r = []
    if include_self:
        r.append(self)
    for c in Person.objects.filter(parent=self):
        _r = c.get_all_children(include_self=True)
        if 0 < len(_r):
            r.extend(_r)
    return r

(再帰やデータが多い場合は、これを使用しないでください...)

Errxによって提案されているようにmpttをまだ推奨しています。

32
sunn0

変更されたプレオーダーツリートラバーサルについて読む必要があります。これがDjango実装です。 https://github.com/Django-mptt/Django-mptt/

15
errx

sunn0の提案は素晴らしいアイデアですが、get_all_children()は奇妙な結果を返します。 [Person1、[Person3、Person4]、[]]のようなものを返します。以下のように変更する必要があります。

def get_all_children(self, include_self=True):
    r = []
    if include_self:
        r.append(self)
    for c in Person.objects.filter(parent=self):
        _r = c.get_all_children(include_self=True)
        if 0 < len(_r):
            r.extend(_r)
    return r
8
SeomGi Han

木の最大の深さがわかっている場合は、次のようなものを試すことができます(テストされていません)。

Person.objects.filter(Q(parent=my_person)|Q(parent__parent=my_person)| Q(parent__parent__parent=my_person))
6

私はこれが古いことを知っています、しかし誰かがただ助けられるかもしれません。

     def get_all_children(self, container=None):
         if container is None:
             container = []
         result = container
         for child in self.children.all():
             result.append(child)
             if child.children.count() > 0:
                 child.get_all_children(result)
         return result

次に、これをproperty(またはcached_propertyそれがあなたのために働くなら)それはどんなインスタンスでも呼び出すことができるようにモデルで。

1
yusuf.oguntola

私は非常によく似た ビジネス上の問題 を持っていました。チームメンバーが与えられた場合、私は彼の下にある完全なチームを見つけることになっていたのです。しかし、多数の従業員がいると、再帰ソリューションが非常に非効率になり、APIがサーバーからタイムアウトエラーを取得していました。

受け入れられたソリューションはノードを取得し、その最初の子に移動し、階層の最下部まで深くなります。次に、2番目の子(存在する場合)に再び戻り、最後まで再び下がります。つまり、すべてのノードを1つずつ探索し、配列内のすべてのメンバーを追加します。これにより多くのdb呼び出しが発生するため、調査するノードの数が非常に多い場合は回避する必要があります。私が思いついた解決策は、ノードをレイヤーごとにフェッチします。 db呼び出しの数は、レイヤーの数と同じです。解決策については、これを見てください SOリンク

0
Shubhanshu

また、QuerySetで記述します。これにより、それらをチェーンできるようになります。そして、私はすべての子供とすべての親の両方の検索に答えを提供します。

class PersonQuerySet(QuerySet):
    def descendants(self, person):
        q = Q(pk=person.pk)
        for child in person.children.all():
            q |= Q(pk__in=self.descendants(child))
        return self.filter(q)

    def ancestors(self, person):
        q = Q(pk=person.pk)
        if person.parent:
            q |= Q(pk__in=self.ancestors(person.parent))
        return self.filter(q)

次に、マネージャーとしてPersonQuerySetを設定する必要があります。

class Person(TimeStampedModel):
    name = models.CharField(max_length=32)
    parent = models.ForeignKey('self', null=True, blank=True, related_name='children')

    people = PersonQuerySet.as_manager()

これが最後のクエリです。

albert_einstein = Person.people.get(name='Albert Einstein')
bernhard_einstein = Person.peole.get(name='Bernhard Caesar Einstein')
einstein_folks = Person.people.descendants(albert_einstein).ancestors(bernhard_einstein)

注:次の解決策は、以前の他の回答と同じくらい遅いです。私は、データベースがその子/親に再発するたびにヒットを検査しました。 (誰かがいくつかの最適化とキャッシュでさらに改善できるなら、これはおそらくより良いでしょうプリフェッチクエリする前の関連データ)。その間、mpttはより実用的です。

0
Yeo