Djangoでデータを取得してみる(外部キー)

データの準備

モデルを作成。

$ vi books/models.py
from django.db import models

class Author(models.Model):
    GENDER = (
        (1, '男'),
        (2, '女')
    )
    name = models.CharField(max_length=100)
    gender = models.IntegerField(choices=GENDER)

class Book(models.Model):
    title = models.CharField(max_length=100)
    price = models.IntegerField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

一旦、DBをまっさらにして、

$ rm db.sqlite3

マイグレーションを実施。

$ python3 manage.py makemigrations
$ python3 manage.py migrate

データを作成し、投入

$ vi books/fixtures/initial_data.json
[
  {"model": "books.author", "pk": 1, "fields": {"name": "太郎", "gender": 1}},
  {"model": "books.author", "pk": 2, "fields": {"name": "花子", "gender": 2}},

  {"model": "books.book", "pk": 1, "fields": {"title": "book1", "price": 1000, "author_id":1}},
  {"model": "books.book", "pk": 2, "fields": {"title": "book2", "price": 2000, "author_id":1}},
  {"model": "books.book", "pk": 3, "fields": {"title": "book3", "price": 3000, "author_id":1}},
  {"model": "books.book", "pk": 4, "fields": {"title": "book4", "price": 4000, "author_id":2}},
  {"model": "books.book", "pk": 5, "fields": {"title": "book5", "price": 5000, "author_id":2}}
]

$ python3 manage.py loaddata initial_data.json

authorテーブル

id name gender
1 太郎 1
2 花子 2

bookテーブル

id title price author_id
1 book1 1000 1
2 book2 2000 1
3 book3 3000 1
4 book4 4000 2
5 book5 5000 2

データの取得

親テーブルのデータは、「book.author.name」みたいな感じで参照できる。

$ vi books/templates/books/book_list.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
</head>
<body>
  {% for book in object_list %}
  {{ book.title }}({{ book.author.name}})<br>
  {% endfor %}
</body>
</html>

f:id:yk5656:20210504155750j:plain

しかし、このままでは、参照する度にSQLが実行される

  1. SELECT … FROM "books_book"
  2. SELECT … FROM "books_author" WHERE "books_author"."id" = '1'
  3. SELECT … FROM "books_author" WHERE "books_author"."id" = '1'
  4. SELECT … FROM "books_author" WHERE "books_author"."id" = '1'
  5. SELECT … FROM "books_author" WHERE "books_author"."id" = '2'
  6. SELECT … FROM "books_author" WHERE "books_author"."id" = '2'

select_related()を付けると、

・・・
class BookList(ListView):
    model = Book
    queryset = Book.objects.all().select_related()

下記の1回のSQLの実行で済む。

SELECT "books_book"."id",
       "books_book"."title",
       "books_book"."price",
       "books_book"."author_id",
       "books_author"."id",
       "books_author"."name",
       "books_author"."gender"
  FROM "books_book"
 INNER JOIN "books_author"
    ON ("books_book"."author_id" = "books_author"."id")

親テーブルで絞り込み

「親のモデル名__カラム名」で絞り込むこともできる。

・・・
class BookList(ListView):
    model = Book
    queryset = Book.objects.filter(author__name='花子')

f:id:yk5656:20210504155808j:plain

この場合、bookの検索時には、下記のSQLが実行される。

SELECT "books_book"."id",
       "books_book"."title",
       "books_book"."price",
       "books_book"."author_id"
  FROM "books_book"
 INNER JOIN "books_author"
    ON ("books_book"."author_id" = "books_author"."id")
 WHERE "books_author"."name" = '''花子'''

データの取得(Author側)

authorも一覧を表示できるよう、urls.pyとviews.pyを修正。

・・・
urlpatterns = [
    path('', views.BookList.as_view(), name='book_list'),
    path('authors', views.AuthorList.as_view(), name='author_list'),
]
・・・
from .models import Author

class BookList(ListView):
    model = Book

class AuthorList(ListView):
    model = Author

「book_set.all」のように指定すると、子のテーブルのデータが取得できる。
※choicesの表示名は「get_カラム名_display」で取得できる。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
</head>
<body>
  {% for author in object_list %}
    {{ author.name }}({{ author.get_gender_display }})<br>
    {% for book in author.book_set.all %}
      ・{{ book.title}}<br>
    {% endfor %}
  {% endfor %}
</body>
</html>

f:id:yk5656:20210504160004j:plain

しかし、このままでは、参照する度にSQLが実行される。

  1. SELECT … FROM "books_author"
  2. SELECT … FROM "books_book" WHERE "books_book"."author_id" = '1'
  3. SELECT … FROM "books_book" WHERE "books_book"."author_id" = '2'

prefetch_relatedを指定すると、

・・・
class AuthorList(ListView):
    model = Author
    queryset = Author.objects.all().prefetch_related("book_set")

2回のSQLで済むようになる。

  1. SELECT … FROM "books_author"
  2. SELECT … FROM "books_book" WHERE "books_book"."author_id" IN ('1', '2')

子テーブルで絞り込み

「子のモデル名__カラム名」で絞り込むこともできる。

・・・
class AuthorList(ListView):
    model = Author
    queryset = Author.objects.filter(book__title="book1")

f:id:yk5656:20210504160016j:plain

この場合、bookの検索時には、下記のSQLが実行される。

SELECT "books_author"."id",
       "books_author"."name",
       "books_author"."gender"
  FROM "books_author"
 INNER JOIN "books_book"
    ON ("books_author"."id" = "books_book"."author_id")
 WHERE "books_book"."title" = '''book1'''