Djangoのモデルで結合関連(多対1)
DjangoのモデルでQuerySetの結合関連(多対1)
すぐに忘れてしまうので、モデルを利用したクエリ処理をを備忘録としてまとめておく
前提となるモデル
class Category(models.Model):
class Meta:
db_table = "category"
name = models.CharField(
max_length=100,
)
def __str__(self):
return self.name
class Author(models.Model):
class Meta:
db_table = "author"
name = models.CharField(
max_length=100,
)
age = models.IntegerField(
)
def __str__(self):
return self.name
class Book(models.Model):
class Meta:
db_table = "book"
title = models.CharField(
max_length=100,
)
price = models.IntegerField(
)
pub_date = models.DateField(
)
author = models.ForeignKey(
Author,
on_delete=models.CASCADE,
)
category = models.ForeignKey(
Category,
on_delete=models.CASCADE,
)
def __str__(self):
return self.title
categoryのレコード
+----+----------+
| id | name |
+----+----------+
| 1 | sf |
| 2 | love |
| 3 | business |
| 4 | mystery |
| 5 | history |
+----+----------+
authorのレコード
+----+-------+-----+
| id | name | age |
+----+-------+-----+
| 1 | Bob | 20 |
| 2 | Tom | 21 |
| 3 | Nancy | 25 |
| 4 | Meg | 20 |
| 5 | Amy | 24 |
| 6 | Cindy | 20 |
+----+-------+-----+
authorのレコード
+----+-------+-------+------------+-----------+-------------+
| id | title | price | pub_date | author_id | category_id |
+----+-------+-------+------------+-----------+-------------+
| 1 | aaa | 1000 | 2020-04-10 | 1 | 1 |
| 2 | bbb | 1200 | 2020-05-05 | 1 | 2 |
| 3 | ccc | 1500 | 2021-10-10 | 2 | 3 |
| 4 | ddd | 1000 | 2020-06-20 | 3 | 2 |
| 5 | eee | 1800 | 2021-08-15 | 3 | 4 |
| 6 | fff | 1400 | 2020-07-20 | 3 | 2 |
| 7 | ggg | 1200 | 2022-03-10 | 4 | 1 |
| 8 | hhh | 1200 | 2022-04-20 | 5 | 4 |
| 9 | iii | 1500 | 2022-08-10 | 5 | 1 |
| 10 | jjj | 1200 | 2022-09-10 | 5 | 1 |
| 11 | kkk | 1000 | 2022-04-05 | 5 | 4 |
| 12 | lll | 1500 | 2022-10-10 | 5 | 2 |
+----+-------+-------+------------+-----------+-------------+
多対1、1対1の関係
多対1の場合、select_related(1側のモデル名) を利用します。
「select_related」は必須でないので、記載しなくても1側を取得可能だが、キャッシュが有効になるのでパフォーマンスが向上します。ループする場合、1+N問題が解決するので、必ず利用します
多側のモデル名.objects.select_related("1側のモデル名").all()
多対1の結合
select_relatedで1側のモデル名を指定します。「JOIN」になります。「author」のみを結合しているので、「category」は結合していない
Book.objects.select_related("author")
SELECT
`book`.`id`,
`book`.`title`,
`book`.`price`,
`book`.`pub_date`,
`book`.`author_id`,
`book`.`category_id`,
`author`.`id`,
`author`.`name`,
`author`.`age`
FROM
`book`
INNER JOIN
`author`
ON
(`book`.`author_id` = `author`.`id`)
多対1で複数の1側を結合
select_relatedで複数の1側のモデル名を指定します。「JOIN」になります。「author」「category」を結合します
Book.objects.select_related("author","category")
SELECT
`book`.`id`,
`book`.`title`,
`book`.`price`,
`book`.`pub_date`,
`book`.`author_id`,
`book`.`category_id`,
`author`.`id`,
`author`.`name`,
`author`.`age`,
`category`.`id`,
`category`.`name`
FROM
`book`
INNER JOIN
`author`
ON
(`book`.`author_id` = `author`.`id`)
INNER JOIN
`category`
ON
(`book`.`category_id` = `category`.`id`)
結合して多側のカラムで検索
多対1で結合して、多側のカラムで検索します。「JOIN」と「WHERE」です。select_relatedとfilterを併用します。順番はどちらでもよい
Book.objects.select_related("author").filter(price=1000)
SELECT
`book`.`id`,
`book`.`title`,
`book`.`price`,
`book`.`pub_date`,
`book`.`author_id`,
`book`.`category_id`,
`author`.`id`,
`author`.`name`,
`author`.`age`
FROM
`book`
INNER JOIN
`author`
ON
(`book`.`author_id` = `author`.`id`)
WHERE
`book`.`price` = 1000
結合して1側のカラムで検索
多対1で結合して、1側のカラムで検索します。「JOIN」と「WHERE」です。select_relatedとfilterを併用します。順番はどちらでもよい。filetrの引数で「1側のモデル名__フィールド名=値」を指定します
Book.objects.select_related("author").filter(author__id=1)
SELECT
`book`.`id`,
`book`.`title`,
`book`.`price`,
`book`.`pub_date`,
`book`.`author_id`,
`book`.`category_id`,
`author`.`id`,
`author`.`name`,
`author`.`age`
FROM
`book`
INNER JOIN
`author`
ON
(`book`.`author_id` = `author`.`id`)
WHERE
`book`.`author_id` = 1
結合して1側のカラムで検索
1側のカラムが「name」で「Amy」と等しい。filterの引数を「1側のモデル名__フィールド名=値」にします
Book.objects.select_related("author").filter(author__name="Amy")
SELECT
`book`.`id`,
`book`.`title`,
`book`.`price`,
`book`.`pub_date`,
`book`.`author_id`,
`book`.`category_id`,
`author`.`id`,
`author`.`name`,
`author`.`age`
FROM
`book`
INNER JOIN
`author`
ON
(`book`.`author_id` = `author`.`id`)
WHERE
`author`.`name` = 'Amy'
結合して複数の検索条件
「category」と「author」を結合します。filterで「category」の「name」と「book」の「price」で検索します。「price」はルックアップタイプの「gte」を利用します
Book.objects \
.select_related("author","category") \
.filter(category__name="mystery",price__gte=1400)
SELECT
`book`.`id`,
`book`.`title`,
`book`.`price`,
`book`.`pub_date`,
`book`.`author_id`,
`book`.`category_id`,
`author`.`id`,
`author`.`name`,
`author`.`age`,
`category`.`id`,
`category`.`name`
FROM
`book`
INNER JOIN
`category`
ON
(`book`.`category_id` = `category`.`id`)
INNER JOIN
`author`
ON
(`book`.`author_id` = `author`.`id`)
WHERE
(
`category`.`name` = 'mystery'
AND
`book`.`price` >= 1400
)
結合に利用したフィールドで検索
結合に利用しているフィールドで検索します。1側のカラムが不要であれば、select_relatedは不要です。当然ですが…。
Book.objects.filter(author=2)
SELECT
`book`.`id`,
`book`.`title`,
`book`.`price`,
`book`.`pub_date`,
`book`.`author_id`,
`book`.`category_id`
FROM
`book`
WHERE
`book`.`author_id` = 2
以下は同じこと
Book.objects.filter(author=2)
Book.objects.filter(author__id=2)
Book.objects.filter(author__pk=2)
filterで1側のカラムで検索
select_relatedしなくても、「1側のモデル名__カラム名=値」でfilterすると結合される。ここでは、「1側のモデル名__カラム名__ルックアップタイプ=値」でfilterしています
Book.objects.filter(author__age__lte=20)
SELECT
`book`.`id`,
`book`.`title`,
`book`.`price`,
`book`.`pub_date`,
`book`.`author_id`,
`book`.`category_id`
FROM
`book`
INNER JOIN
`author`
ON
(`book`.`author_id` = `author`.`id`)
WHERE
`author`.`age` <= 20
結合と件数
結合して、検索して、件数を行います
Book.objects.select_related("author").filter(author__name="Amy").count()
SELECT
COUNT(*) AS `__count`
FROM
`book`
INNER JOIN
`author`
ON
(`book`.`author_id` = `author`.`id`)
WHERE
`author`.`name` = 'Amy'
valuesでカラム名を指定して取り出す
「author」を結合します。filterで「author」の「name」で検索します。valuesで特定のカラムを取り出します。「book.id」「book.title」「book.price」「author.name」を取り出します。
valuesの引数で各フィールドを指定します。多側のカラム名のみを指定します。1側は「モデル名__フィールド名」で指定します
valuesなので、ディクショナリ形式のQuerySetが返ります
Book.objects \
.select_related("author") \
.filter(author__name="Amy") \
.values("id","title","price","author__name")
SELECT
`book`.`id`,
`book`.`title`,
`book`.`price`,
`author`.`name`
FROM
`book`
INNER JOIN
`author`
ON
(`book`.`author_id` = `author`.`id`)
WHERE
`author`.`name` = 'Amy'
Discussion