19
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【鬼滅の刃で学ぶDjango】順参照・逆参照のクエリセット取得方法

Last updated at Posted at 2020-03-19

Django使ってる?

PythonのWebフレームワークでお馴染みのDjango。『いつ、どのサイトにどんなツールが導入されたかが分かるSimilarTech』によれば、シェアでは殆どの国でRuby On Railsに劣るものの、トレンドはDjangoが右肩上がりの模様。
image.png

さて、本記事の対象者はDjangoをある程度使った事がある人、MTV(あるいはMVC)モデルが多少なりとも分かる人向けの内容となっております。まだ触ったことないよって人は、公式サイトのチュートリアルから始めてみましょう。

順参照と逆参照

まず下記の3つのモデル(鬼殺隊員、階級、呼吸)があるとします。

kimetsu_no_models.py
from django.db import models


class KisatsuMember(models.Model):
    """ 鬼殺隊員モデル """
    id = models.UUIDField()
    # 性 ex) 竈門、我妻、胡蝶
    last_name = models.CharField(max_length=20)
    # 名 ex) 炭治郎、善逸、しのぶ
    first_name = models.CharField(max_length=20)
    # 階級
    rank = models.ForeignKey(Rank, on_delete=models.PROTECT)
    # 呼吸
    breath = models.ForeignKey(Breath, on_delete=models.PROTECT, null=True)
    # 現役フラグ
    is_active = models.BooleanField(default=True)

    class Meta:
        db_table = 'kisatsu_member'


class Rank(models.Model):
    """ 階級モデル """
    id = models.UUIDField()
    # 階級名 ex) 柱、癸
    name = models.CharField(max_length=10)

    class Meta:
        db_table = 'rank'


class Breath(models.Model):
    """ 呼吸モデル """
    id = models.UUIDField()
    # 呼吸名 ex) 水、雷、獣
    name = models.CharField(max_length=10)
    # 型の数 ex) 6, 10, 11
    number_of_types = models.IntegerField()

    class Meta:
        db_table = 'breath'

順参照の例

鬼殺隊員から呼吸へ、というように子から親のテーブルの値を参照することを『順参照』と言います。
例として、『水』の呼吸を使う鬼殺隊員の姓・名・型の数を順参照で取得するなら下記のようになります。
※1 後続の処理でループさせる場合はN+1問題の為、select_relatedメソッドでの結合かリスト変換が推奨されます。

mizu_no_views.py
water_breath_member = KisatsuMember.objects \
    .filter(breath__name="") \
    .values("last_name",
            "first_name",
            "breath__number_of_types")
print(water_breath_user)
# <QuerySet [{'last_name': '竈門', 'first_name': '炭治郎', 'breath__number_of_types': 10},
#            {'last_name': '鱗滝', 'first_name': '左近次', 'breath__number_of_types': 10},
#            {'last_name': '冨岡', 'first_name': '義勇', 'breath__number_of_types': 11}]>

発行クエリ

mizu_no.sql
SELECT
    kisatsu_member.last_name,
    kisatsu_member.first_name,
    breath.number_of_types
FROM
    kisatsu_member
    INNER JOIN
        breath
    ON  kisatsu_member.breath_id = breath.id
WHERE
    breath.name = '水'
;

逆参照の例

反対に親から子、階級から鬼殺隊員といった参照のことを『逆参照』と言います。
今度は少し複雑な取得の仕方をしてみます。
階級が『柱』かつ『現役』の鬼殺隊員の『水』以外の呼吸の名前を取得するなら下記のようになります。
逆参照はunderscore抜きのテーブル名を結合させる点が味噌です。
※2 後続の処理でループさせる場合はN+1問題の為、prefetch_relatedメソッドでの結合かリスト変換が推奨されます。
※3 prefetch_related("kisatsumember_set")を使って結合する場合、kisatsumember_set__<カラム名>といった形で結合先の値を扱う事が出来ます。

hashira_no_views.py
active_hashira_breathes = Rank.objects \
    .filter(name="",
            kisatsumember__is_active=True) \
    .exclude(kisastumember__breath__name="") \
    .values("kisastumember__breath__name")
print(active_hashira_breathes)
# <QuerySet [{'kisastumember__breath__name': '炎'},
#            {'kisastumember__breath__name': '恋'},
#            {'kisastumember__breath__name': '霞'}...]>

発行クエリ

hashira_no.sql
SELECT
    breath.name
FROM
    `rank`
    INNER JOIN
        kisatsu_member
    ON  kisatsu_member.rank_id = `rank`.id
    INNER JOIN
        breath
    ON  kisatsu_member.breath_id = breath.id
WHERE
    `rank`.name = '柱'
AND kisatsu_member.is_active = 1
AND breath.name <> '水'
;

※4 Djangoはモデル定義において、外部参照キーがnull=Trueか否かによって、INNER JOINやLEFT OUTER JOIN 等、内部的に判断してSQLに変換していたはずです。(うろ覚え)

一つの道を極めるんじゃ

Pythonしかろくに書けませんが、今後とも精進致します!

image.png

19
10
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
19
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?