Angular, Blockchain, Science とか

Angular, Blockchain, Science全般 の情報を主に書いていきます。

Django クエリセット まとめ annotate, select_related(),

Djangoのクエリセットの中でいくつかをまとめました。

anotate

aggregateが全体の集計を行うのに対し、annotateは各レコードごとの集計を行うことができる。

# 注釈付けされるクエリセットを組み立てる
>>> q = Book.objects.annotate(Count('authors'))
# クエリセットの最初のオブジェクトを取得
>>> q[0]
<Book: The Definitive Guide to Django>
>>> q[0].authors__count
2
# クエリセットの2番目のオブジェクト取得
>>> q[1]
<Book: Practical Django Projects>
>>> q[1].authors__count
1

aggregate() と同様に, 注釈(annotation)の名前は集約関数の名前と集約される フィールド名で自動的に作成されます。 名前をオーバーライドできる

>>> q = Book.objects.annotate(num_authors=Count('authors'))
>>> q[0].num_authors
2
>>> q[1].num_authors
1

select_related()

自動的に外部キーのリレーションを「追跡」し、クエリを実行したときにリレーショ ン先のオブジェクトも加えて選択するような QuerySet を返します。 これはパフォーマンスを向上させるための機構で、クエリは (ときに非常に) 巨大 になりますが、以後の外部キーへのリレーションでデータベースクエリが必要なく なります。

# データベースを操作します。
e = Post.objects.get(id=5)

# リレーション先の Blog オブジェクトを取得するために再度データベースを
# 操作します。
b = p.blog

一方、 select_related() を使った照合では:

# データベースを操作します。
p = Post.objects.select_related().get(id=5)

# e.blog は上のクエリで取得済みなので、データベースを操作しません。
b = p.blog

select_related は可能な限り外部キーを追跡することに注意してください。以 下のようなモデル:

class City(models.Model):
    # ...
    pass

class Author(models.Model):
    # ...
    hometown = models.ForeignKey(City)

class Book(models.Model):
    # ...
    author = models.ForeignKey(Author)

の場合、 Book.objects.select_related().get(id=4) を実行すると、リレーションの張られた Author に加えて City もキャッシュします:

b = Book.objects.select_related().get(id=4)
p = b.author         # データベースを操作しません。
c = p.hometown       # データベースを操作しません。

b = Book.objects.get(id=4) # select_related() しない場合
p = b.author         # データベースを操作します。
c = p.hometown       # データベースを操作します。

通常、 select_related() を使うと、データベースの呼び出し回数を減らせる ので、大幅にパフォーマンスを向上できます。しかし、リレーションが深くネスト しているような状況では、 select_related() が追跡するリレーションが「多 すぎる」ために、巨大なクエリを生成してしまい、結果的にパフォーマンスの低下 を招く場合があります。

こうした状況に対応するため、 select_related() に depth 引数を指定す ると、以下の例のようにリレーションを何「レベル」まで追跡するかを制御できま す:

b = Book.objects.select_related(depth=1).get(id=4)
p = b.author         # 追跡済みのリレーション。データベースを操作しません。
c = p.hometown       # 未追跡のリレーション。データベースを呼び出します。

prefetch_related

foreign keyの逆参照を取ってきたりMany-to-Manyの参照先を取得する際に、prefetch_relatedを使う。

class ModelA(models.Model):
    pass

class ModelB(models.Model):
    a = ForeignKey(ModelA)

ModelB.objects.select_related('a').all() # Forward ForeignKey relationship
ModelA.objects.prefetch_related('modelb_set').all() # Reverse ForeignKey relationship

stackoverflow.com

ここが分かりやすい。