\ ポイント最大11倍! /

【Django】QuerySet チートシート

  • Django の QuerySet 操作を逆引きで調べたい!

このような方に向けて、Django でよく使う操作を中心にまとめました。

各メソッドの説明はジャンルに分けてありますので、以下の目次からお目当てのコンテンツにジャンプしてご利用ください!

Contents
  1. 基本の取得方法
  2. レコードを「文字列」で絞り込んで取得
  3. レコードを「数字」で絞り込んで取得
  4. その他の絞り込み
  5. 並び替え
  6. 取得形式を指定する
  7. クエリセットを使って計算する
  8. SQL クエリ発行回数の節約
  9. レコードの更新(追加・削除)
  10. レコードの存在確認

基本の取得方法

まずは超基本的なクエリセットの取得方法を解説します。

Django 公式チュートリアルにも頻出なので説明不要かもしれませんが、基本なので念のためです。

全レコードを取得する

全レコードを取得するには、all() メソッドを使います。

余談ですが、objects はマネージャーオブジェクトと呼ばれていて Django コードを解釈して SQL を発行してくれるという役割があります。

queryset = Model.objects.all()

例えば Model テーブルに 10 個のレコードがあれば、10 個分すべての QuerySet オブジェクトが返ってきます。

Django では遅延評価がされるので、上記コードが実行されたタイミングでは SQL クエリは発行されません。

プライマリーキーを指定して取得

プライマリキーを指定して、特定のレコードを取得することもできます。

model_instance = Model.objects.get(pk=1)

ここでいう get() は Python の辞書に対して使うメソッドとは違って、Django の objects マネージャを通して使われるものであることに注意です。(つまり、第二引数にデフォルト値を取ることはできない)

また、get() でプライマリキーを指定する場合には、クエリセットではなく単一のモデルインスタンスが返ります。

そのため、ループできない(イテラブルではない)ことにも注意が必要です。

該当する pk が存在しない場合には DoesNotExist エラーが、複数のレコードが存在する場合には MultipleObjectsReturned エラーが発生します。

レコードを「文字列」で絞り込んで取得

続いて、「文字列」を基準に絞り込んでレコードを取得する方法をご紹介します。

完全一致で絞り込む

filter() メソッドの引数に、「フィールド名 = 絞り込みたい文字列」を渡しましょう。

queryset = Model.objects.filter(name="john")

上記を実行すると、name が John のレコードだけが取得されます。

AND 条件で絞り込んで取得

「 A かつ B 」のような AND 条件で絞り込みたい場合には、フィールドと条件をカンマ区切りで列挙します。

queryset = Model.objects.filter(name="john", band="beatles")

例では name が “John” で、かつ band が “beatles” のオブジェクトを取得します。

OR 条件で絞り込んで取得

「A または B」のような OR 条件で絞りたい場合には、フィールド名の後に “__in” をつけます。

queryset = Model.objects.filter(name__in=["john","george"])

この、アンダースコア2つの後に挙動を指示する部分は「フィールドルックアップ」と呼ばれます。

複数のフィールドにまたがって OR 条件で絞り込んで取得

複数フィールドにまたがって OR 条件を指定するには、Qオブジェクトと | (パイプ)を組み合わせて表現します。

from django.db.models import Q
queryset = Model.objects.filter(Q(name="john") | Q(age=30))

ちなみに OR 以外を表現する方法についても触れておきます。

  • AND
    • Qオブジェクト同士をカンマで区切る
    • objects = Model.objects.filter(Q(name=”john”), Q(age=30))
  • NOT
    • Qオブジェクトの先頭に – (ハイフン)をつける
    • objects = Model.objects.filter(~Q(name=”john”))

パイプやハイフンを使うところは、やや Pandas に近いかもしれません。

なお、__in では単一のフィールド内でしか「または」条件が指定できませんでした。

前方一致で絞り込む

ある文字列から始まるレコードを取得するには、”startswith” を使います。

# name が "jo" で始まるレコードを取得
queryset = Model.objects.filter(name__startswith="jo")

正規表現でいうところの^と考えるとわかりやすいです。

後方一致で絞り込む

ある文字列で終わるレコードを取得するには、”endswith” を使います。

# name が "non" で終わるレコードを取得
queryset = Model.objects.filter(name__endswith="non")

正規表現でいうところの$と考えるとわかりやすいです。

中間一致で絞り込む

指定した文字列を含むかどうかを判定するのが contains です。

そのため、中間一致というよりは、文字列の存在をチェックするといった方がわかりやすいかもしれません。

# name に "hn" を含むレコードを取得
queryset = Model.objects.filter(name__contains="hn")

もし、大文字・小文字を無視したい場合には icontains を使うことで実現できます。

# 大文字・小文字を無視する
queryset = Model.objects.filter(name__icontains="hn")

レコードを「数字」で絞り込んで取得

数値型フィールドを使って、数字の大小関係で絞り込む方法です。

より大きい

“__gt” は “greater than” の略で、「より大きい」という意味になります。

# 30 より大きい
queryset = Model.objects.filter(age__gt=30)

上記の例では “30” は含まず、30より大きい数値を持つレコードに絞り込まれます。

より小さい

“__lt” は “less than” の略で、「より小さい」という意味になります。

# 30 より小さい
queryset = Model.objects.filter(age__lt=30)

上記の例では “30” は含まず、30より小さい数値を持つレコードに絞り込まれます。

以上

“__gte” は “greater than equal” の略で、「以上」という意味になります。

# 30 以上
queryset = Model.objects.filter(age__gte=30)

上記の例では “30” を含む、30以上の数値を持つレコードに絞り込まれます。

以下

“__lte” は “less than equal” の略で、「以下」という意味になります。

# 30 以下
queryset = Model.objects.filter(age__lte=30)

上記の例では “30” を含む、30以下の数値を持つレコードに絞り込まれます。

範囲

範囲を指定する場合は range を用います。

# 30 から 50
queryset = Model.objects.filter(age__range=(30, 50))

上記の例では “30” と “50” を含む、30から50の間を数値として持つレコードに絞り込まれます。

その他の絞り込み

これまでご紹介してきた以外の絞り込み方法をご紹介します。

最新・最古のレコードを取得する

“created_at” という DateField がある場合には、次のように日時が最も早い・遅いという基準でレコードを取得できます。

# 最新のレコードを取得する
latest_instance = Model.objects.latest("created_at")

# 最古のレコードを取得する
oldest_instance = Model.objects.earliest("created_at")

最初・最後のレコードを取得する

クエリセットオブジェクトの先頭にあるもの、または最後にあるものを取得する場合です。

# 最初のレコードを取得する
first_instance = Model.objects.all().first()

# 最後のレコードを取得する
last_instance = Model.objects.all().last()

実務的には first() を使うことが多いイメージです。
この後に if 文で null チェックを行うという流れが多いと思います。

並び替え

並び替えの方法を解説します。

昇順でソート

昇順の場合には order_by() メソッドの引数にフィールド名を指定します。

# name を昇順でソート
queryset = Model.objects.all().order_by("name")

降順でソート

降順の場合には、フィールド名の前に – (ハイフン)をつけてください。

# name を降順でソート
queryset = Model.objects.all().order_by("-name")

複数フィールドを基準にソートする

優先順位に従ってフィールド名をカンマ区切りで書いていきます。

queryset = Model.objects.all().order_by("name", "age")

上記の例ではまず “name” で並び替えをしてから “age” で並び替えを行います。

取得形式を指定する

クエリセット以外の形式で取得する方法をご紹介します。

辞書形式で取得する

values() とすることで、辞書形式で取得ができます。

# 辞書で取得
model_dict = Model.objects.values()

values() メソッドはデフォルトでクエリセットの全レコードを取得するので、all() メソッドは不要です。

特定カラムをリストで取得する

あるカラムのデータをリストで取得したい場合には、values_list() メソッドを使います。

# age 列をリスト型で取得する
age_list = Model.objects.values_list("age", flat=True)

flat=True を外すと、リスト型ではなくタプルとして返ります。

クエリセットを使って計算する

計算をすることもできます。

合計値を求める

特定フィールドで合計値を計算する場合、aggregate() メソッドを使います。

from django.db.models import Sum

total_price = Product.objects.aggregate(Sum("price"))
print(total_price)
# 実行結果
# {"price__sum": 合計値}

以下のようにすると、戻り値の辞書のキーを変えることもできます。

from django.db.models import Sum

total_price = Product.objects.aggregate(total_price=Sum("price"))
print(total_price)
# 実行結果
# {"tota_price": 合計値}

平均値を求める

from django.db.models import Avg
average = Product.objects.aggregate(Avg("price"))

最大値を求める

from django.db.models import Max
max = Product.objects.aggregate(Max("price"))

最小値

from django.db.models import Min
result = Product.objects.aggregate(Min("price"))

SQL クエリ発行回数の節約

Django では SQL クエリを然るべきタイミングで実行する「遅延評価」という方式を採用しています。

そのため、ForeignKey や ManyToMany フィールドで定義したデータを呼び出す際にはここで紹介するメソッドを利用してデータベースからデータを効率的に取得するのがおすすめです。

ForeignKey のクエリ発行回数を減らす

ForeignKey で定義したフィールドの呼び出しには、select_related() を用います。

queryset = Model.objects.select_related("band").all()

ManyToMany のクエリ発行回数を減らす

ManyToMany で定義したフィールドの呼び出しには、prefetch_related() を使います。

queryset = Model.objects.prefetch_related("band").all()

レコードの更新(追加・削除)

レコードを追加する

# レコードを追加 その1
Model.objects.create(name="John Lennon", age=30, part="guitar")

# レコードを追加 その2
model_instance = Model(name="John Lennon", age=30, part="guitar")
model_instance.save()

レコードがあれば取得して、なければ追加する

該当のレコードがある場合にはモデルインスタンスと、False を返します。

一方で、該当のレコードがない場合には作成の上モデルインスタンスを返し、is_created としてTrue を返す事になります。

# レコードに存在しなければ作成
model_instance, is_created = Model.objects.get_or_create(name="John Lennon", age=30, part="guitar")

特定レコードの値を書き換える

# 変更したいレコードを取得する
model_instance = Model.objects.get(pk=1)
# 内容を変更する
model_instance.name = "Ringo Star"
# 変更を反映する
model_instance.save()

特定のレコードの値を削除する

# 変更したいレコードを取得する
model_instance = Model.objects.get(pk=1)
# 削除する
model_instance.delete()

複数のレコードを一度に削除する

複数レコードを一度に削除する場合も delete() メソッドで行います。

# 変更したいレコードを取得する
queryset = Model.objects.filter(band="beatles")
# 削除する
queryset.delete()

レコードを更新する

レコードを更新するには update メソッドを使います。

# 最初のオブジェクトを取得
model_instance = Model.objects.all().first()
# band フィールドを Beatles に更新
model_instance.update(band="Beatles")

複数レコードを更新する

複数レコードを一括で変更することもできます。

# 最初のオブジェクトを取得
queryset = Model.objects.all()
# 全レコードの band フィールドを Beatles に更新
queryset.update(band="Beatles")

選択された行を他のデータベーストランザクションから一時的にロックする

select_for_update() メソッドを用います。

@transaction.atomic
def my_function():
    # データベース操作
    instance = Model.objects.select_for_update().get(pk=1)
    # ここから先、instance に対する操作が制限される
    instance.field += 1
    instalnce.save()

上記の通りで、select_for_update() が実行された段階でそのインスタンスへのアクセスが制限されるので、他のトランザクションがこのインスタンスに操作を加えようとすると待機状態になります。

なお、待機ではなく即座いにエラーを返したい場合には以下のように nowait=True とします。

@transaction.atomic
def my_function():
    # データベース操作
    instance = Model.objects.select_for_update(nowait=True).get(pk=1)
    # ここから先、instance に対する操作が制限される
    instance.field += 1
    instalnce.save()

@transaction.atomic を使っているので、関数内で何がしかの問題があればロールバックされます。

レコードの存在確認

特定のレコードが存在するかを確認する

is_exists には True または False のBool 値が入ります。

# 存在を確認する
is_exists = Model.objects.filter(name="John").exists()

この記事が気に入ったら
フォローしてね!

シェア・記事の保存はこちら!

この記事を書いた人

karo@プログラマのアバター karo@プログラマ プログラマ

「書くことで人の役にたつ」をモットーに活動中。
本職はプログラマで、Pythonが得意。
基本情報技術者試験合格。

コメント

コメントする

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)