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

# Django使ってる？
PythonのWebフレームワークでお馴染みのDjango。『いつ、どのサイトにどんなツールが導入されたかが分かる[SimilarTech](https://www.similartech.com/compare/django-vs-ruby-on-rails)』によれば、シェアでは殆どの国でRuby On Railsに劣るものの、トレンドはDjangoが右肩上がりの模様。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286189/db203405-770d-4477-7529-640873782b69.png)

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

# 順参照と逆参照
まず下記の３つのモデル（鬼殺隊員、階級、呼吸）があるとします。


```python: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メソッドでの結合かリスト変換が推奨されます。

```Python: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}]>
```

発行クエリ

```sql: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__<カラム名>といった形で結合先の値を扱う事が出来ます。

```Python: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': '霞'}...]>
```

発行クエリ

```sql: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286189/679dc6fe-22c6-b4e3-6054-d7d9f5d5b3e3.png)