1モデル内で同じモデルに対するmanytomanyを複数持たせてクエリ最適化
受付中
回答 2
2022/06/03 22:20
質問内容

実現したいこと

2点ございます。

1. 同じモデルに対するmanytomanyを複数持たせたい。

モデルとして成立はするのですが、無駄なクエリが発生してしまっています。<br /> 解消したいのですが、今回のようなケースだとORMだけだと厳しいのでしょうか。

発生している問題

M2M先のモデルで外部参照しているモデルを参照する際に、同じ内容でJOINするが別のクエリとして扱われてしまっている。

ソースコード

# models.py
class Parent():
    child_a = models.ManyToManyField(Child)
    child_b = models.ManyToManyField(Child)

class Child():
    hoge = models.ForeignKey()
    fuga = models.ForeignKey()

# serializer.py
class ParentSerializer():
    child_a = ChildSerializer()
    child_b = ChildSerializer()
    
class ChildSerializer():
    hoge = HogeSerializer()

class HogeSerialzer():
    class Meta:
           fields = '__all__'
# views.py
class ParentView():
    queryset = Parent.objects.prefetch_related(
        Prefetch('child_a', Child.objects.select_related('hoge', 'fuga'),
        Prefetch('child_b', Child.objects.select_related('hoge', 'fuga'),
        )

試したこと

# m2mはselect_relatedできないので動かない
queryset = Parent.objects.prefetch_related('child_a','child_b').select_related('child_a__hoge')

#
queryset = Parent.objects.prefetch_related(
    Prefetch('child_a', Child.objects.select_related('hoge', 'fuga'),
    Prefetch('child_b', Child.objects.select_related('hoge', 'fuga'),
        )

補足情報

Childモデルのhogeとfugaを参照するクエリが2回ずつ走ってしまいます。 child_aの方で走らせたクエリを再利用することは可能でしょうか?

class Parent():
    child = models.M2M()

class Child():
    hoge = models.ForeignKey()
    fuga = models.ForeignKey()
    prefix = models.CharField()

のようにしてchild側にprefixをもたせることも考えましたが、できれば避けたいです。

2. M2Mのフィールドからあえて1つだけデータを取りたい

こちらは解消できました。

serializers.SerializerMethodField()に対するquerysetのprefetchのかけ方が悪かったようでした。

選択としては誤っていなかったようでした。

発生している問題・ソースコード

上記Parent Serializerを例に取ると、

# models.py
class Child():
    hoge = models.ForeignKey()
    fuga = models.ForeignKey()
    is_first = models.BooleanField() # prefixとは別でつけたい

class ParentSerializer():
    child_a = ChildSerializer(many=True) # is_first=Trueのもののみ取りたいが、全部取られてしまう
    

理想の動きとしては、Modelにも手を加えると

class Child():
    parent = models.ForeignKey("Parent") #追加
    hoge = models.ForeignKey()
    fuga = models.ForeignKey()
    is_first = models.BooleanField() 

class ParentSerializer():
    child_a = serializers.SerializerMethodField()

    def get_child_a(self, obj):
        return Child.objects.get(parent=obj.id, is_first=True) # 1つだけ値を返してくれる

動きの理想は下の方ですが、SerializerMethodFieldの方はprefetchできていないためか、1回1回クエリを飛ばしてしまっています。

a. n+1?が起こらないような select_related, prefetch_relatedの指定をする b. (many=True)のM2Mのシリアライザーで絞り込みをできるようにする

のどちらかの解決策が取れればと考えていますが、うまくいきません。

複雑なものは生のSQLを書いたほうがよいのでしょうか。

管理(みやすさ)観点からできればORMで実現したいと考えています。

知見ございましたらご回答のほどよろしくお願いいたします。

回答 2
ベストアンサーを選択すると、解決済みとなります。
nodata
まだ回答がありません
回答
nodata
回答するにはログインが必要です