211
194

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Djangoでprefetch_relatedを便利に使う

Last updated at Posted at 2015-04-12

社内でDjangoプロジェクト増えてきて、イケてないクエリも増えてきそうなので簡単にメモを残す。

ORMを使って何も考えずにforeign keyの逆参照を取ってきたりMany-to-Manyの参照先を取得する際に、ループの度にクエリを飛ばして悲惨なことになるので、prefetch_relatedを使っとけという話

普通のForeign Keyならselect_relatedで必要な階層分引っ張ることが出来る。

普通に使う

とりあえずこんなモデル

from django.db import models

class Campaign(models.Model):
    name = models.CharField(max_length=255)
    created_at = models.DateTimeField()

class Creative(models.Model):
    name = models.CharField(max_length=255, default="")
    campaign = models.ForeignKey(Campaign)
    created_at = models.DateTimeField()

CreativeからCampaignを引く場合はselect_relatedを使ってあげればループの度にCampaignをselectせずにjoinしてまとめて取ってきてくれる。

for creative in Creative.objects.all().select_related():
	print(creative.campaign)

CampaignからCreativeの一覧を引っ張る場合はprefetch_relatedを使うと良い

for campaign in Campaign.objects.all().prefetch_related("creative_set"):
	print(campaign.creative_set.all())

joinではなく一度全部Campaignを引っ張ってきて、campaign.idのリストをwhere inして引っ張ってきている様子。まぁ毎回飛ぶより良いし大抵の場合において問題なさそう。

ちなみにcreativeでcampaignを絞るのは普通にクエリでかける

Campaign.object.filter(creative_set__name="aaa")

Prefetchオブジェクトを使う

単純にprefetch_relatedを使うだけだと、XXXX_set.all()の物しか取得できない。creative_setに対して絞込等を掛けるとループの度にqueryが飛んでとても悲しいことになる。

for campaign in Campaign.objects.all().prefetch_related("creative_set"):
	print(campaign.creative_set.filter(name__startswith="hoge"))

そこで、逆参照やMany-to-Manyの先をfilter使って絞り込みたい場合や、並び順を指定したい場合は、Djangoの1.7から入ったPrefetchオブジェクトを利用する。こいつのお陰でだいぶDjangoのORMもマシになった気がする。

from django.db import models
from django.db.models import Prefetch

for campaign in Campaign.objects.all().prefetch_related(Prefetch("creative_set", queryset=Creative.objects.filter(name__startswith="hoge").order_by("-created_at"), to_attr="creatives")):
	if len(campaign.creatives) > 0:
        print(campaign.id, creatives[0].id)

これだとCampaignが何個あってもクエリが2回しか飛ばなくてお得。

クエリ見る

クエリのチューニングをする際は、Django Shellを使ってクエリの確認をすることになると思うので、shellでクエリをぶっ叩いた時にSQL文が表示されるようにloggerをいじると良い。普通にやっているとログの設定をいじっているとは思うが、クエリの調査の時だけはdjango.db.backendsのloglevelをDEBUGにしておくと幸せになる。

settings.py
LOGGING = {
    'version': 1,
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
        }
    },
    'loggers': {
        'django.db.backends': {
            'level': 'DEBUG',
            'handlers': ['console'],
        },
    }
}

view単位のチューニングの場合は、django-debug-toolbarを使ってクエリの確認をするのが基本

211
194
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
211
194

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?