見出し画像

DjangoORMの各種メソッドを習得するレシピ

この技術ノートでは、DjangoORMで提供される様々なメソッドの利用方法を学ぶことができます。

以下のメソッドについて具体的な利用方法を交えて解説します。

1.事前準備

ORMの各メソッドの動作を確認するための環境の準備を行います。

今回は、以下のような学生とスクールの情報を定義したDjangoのモデルを使います。

from django.db import models
GRADE=(
        ("秀","秀"),
        ("優","優"),
        ("良","良"),
        ("可","可"),
        ("不","不"),
    )
BROOD_GROUP=(
        ("A","A"),
        ("B","B"),
        ("O","O"),
        ("AB","AB"),
    )
class School(models.Model):
    name = models.CharField(max_length=100,verbose_name="スクール名")
    email = models.EmailField(null=True, blank=True, verbose_name="メールアドレス")
    address = models.TextField(verbose_name="住所")
    class Meta:
        verbose_name ="スクールデータ"
        verbose_name_plural ="スクールデータ"
    def __str__(self):
        return self.name
class Student(models.Model):
    name = models.CharField(max_length=100,verbose_name="氏名")
    grade = models.CharField(verbose_name="成績", max_length=5, choices=GRADE)
    school = models.ForeignKey(School, on_delete=models.CASCADE, verbose_name="スクール名")
    blood_group = models.CharField(max_length=10,verbose_name="血液型", choices=BROOD_GROUP)
    mobile = models.CharField(max_length=20)
    address = models.TextField(verbose_name="住所")
    class Meta:
        verbose_name ="学生データ"
        verbose_name_plural ="学生データ"
    def __str__(self):
        return self.name

動作確認用のDjangoプロジェクトと初期データを準備したGitリポジトリを用意してありますので、以下の手順で環境を準備しましょう。

まず、任意のディレクトリ上で以下のコマンドを実行してリポジトリをクローンします。

git clone https://github.com/sinjorjob/django-orm-method-learn-project.git
cd django-orm-method-learn-project

仮想環境を作成してアクティベートし、Djangoをインストールします。

python -m venv venv
venv\scripts\activate
pip install django

マイグレーションを実行します。

python manage.py makemigratinos
python manage.py migrate

管理者ユーザを作成します。

python manage.py createsuperuser

動作確認用のデモデータをインポートします。

python manage.py loaddata app/fixtures/sample_data.json
Installed 18 object(s) from 1 fixture(s)

開発サーバを起動したらhttp://127.0.0.1:8000/adminにアクセスして管理者ユーザでログオンします。

python manage.py runserver

学生データを確認して、以下のように15件データが登録されていることが確認できればOKです。

次にCtrl+Cで開発サーバを停止した後以下のコマンドでDjangoのシェルモードを起動しておきます。

python manage.py shell

また、モデルの定義をインポートしておきます。

from app.models import *

以上で事前準備は完了です。

以降は、このシェルモード上でデータベースに対してクエリを発行しながら動作確認を行います。

2.ORMメソッドの動作確認

filterメソッド

filterメソッドは特定のカラム名で条件をフィルタリング検索したい場合に使います。
例えば、成績が「」の人だけを取得したい場合は以下のようになります。

Student.objects.filter(grade='秀')

実行結果は以下の通り成績が「」である5名がヒットします。

<QuerySet [<Student: 斎藤 隆>, <Student: 寺田 淳子>, <Student: 桑原 真紀子>, <Student: 猿川 孝>, <Student: 小池 優子>]

getメソッド

getメソッドは検索の結果1レコードだけを返すことが前提の場合に使います。

例えば氏名が「川口 健吾」という人を検索する場合は以下の通りです。


 Student.objects.get(name='川口 健吾')

実行結果は以下の通り1名のデータがHITします。

<Student: 川口 健吾>

なお、もし同姓同名のデータが存在した場合は以下のようなエラーが発生します。

raise self.model.MultipleObjectsReturned(
app.models.Student.MultipleObjectsReturned: get() returned more than one Student -- it returned 2!

allメソッド

allメソッドはその名の通り、すべてのデータを取得したい場合に利用します。
Shcoolテーブルから全データを取得する場合は以下のコードを実行します。

School.objects.all()

実行結果は以下の通りすべての大学情報(3件)が取得できます。

<QuerySet [<School: 東京大学>, <School: 京都大学>, <School: 大阪大学>]>

updateメソッド

すべてのオブジェクトに特定の値をセットしたい場合は、updateメソッドを使います。

例えば、生徒全員の成績を「」にリセットしたいといった場合は以下のように実行します。

まず、変更前の全員の成績を確認します。

>>> all_student = Student.objects.all()
>>> for student in all_student:
...     print(f"{student.name}:成績={student.grade}")
...
斎藤 隆:成績=秀
山田 高氏:成績=優
小林 あゆみ:成績=優
小野寺 佐由子:成績=良
伊藤 朝子:成績=良
寺田 淳子:成績=秀
菊池 聡:成績=不
原田 卓:成績=良
桑原 真紀子:成績=秀
三谷 景子:成績=可
斎藤 景子:成績=可
猿川 孝:成績=秀
小池 裕也:成績=良
小池 優子:成績=秀
川口 健吾:成績=優

次に全員の成績を「」に設定するには以下のコマンドを実行します。

Student.objects.all().update(grade="可")

再度、全員の成績を確認してみましょう。

>>> all_student = Student.objects.all()
>>> for student in all_student:
...     print(f"{student.name}:成績={student.grade}")
...
斎藤 隆:成績=可
山田 高氏:成績=可
小林 あゆみ:成績=可
小野寺 佐由子:成績=可
伊藤 朝子:成績=可
寺田 淳子:成績=可
菊池 聡:成績=可
原田 卓:成績=可
桑原 真紀子:成績=可
三谷 景子:成績=可
斎藤 景子:成績=可
猿川 孝:成績=可
小池 裕也:成績=可
小池 優子:成績=可
川口 健吾:成績=可
>>>

上記の通り、全員の成績が「可」に変更されたことが確認できます。

データを書き換えてしまったので、一旦adminサイト上から生徒データをすべて削除した後、以下のコマンドを実行して初期データをインポートしてきましょう。

python manage.py loaddata app/fixtures/sample_data.json

以下のように表示されればOKです。

Installed 18 object(s) from 1 fixture(s)

deleteメソッド

eleteメソッドはその名の通りデータを削除したい場合に使います。

ここでは氏名が「斎藤 隆」であるデータ1件を削除してみます。
まず該当データがあるか確認します。

>>> Student.objects.filter(name= "斎藤 隆")
<QuerySet [<Student: 斎藤 隆>]>

上記の通り1件データがHITしたことが確認できます。
次にdeleteメソッドを使ってデータを削除した後、再度、氏名が「斎藤 隆」であるデータを確認してみます。

>>> Student.objects.filter(name= "斎藤 隆").delete()
(1, {'app.Student': 1})
>>> Student.objects.filter(name= "斎藤 隆")
<QuerySet []>

上記の通りdeleteメソッドを実行した後はQuerysetに1件もデータがかえってこない(=削除された)ことが確認できます。

excludeメソッド

excludeメソッドは指定した値を除いたクエリセットを返します。
まずは生徒全員の情報を取得して確認します。

>>> queryset = Student.objects.all()
>>> for student in queryset:
...     print(f"{student.name}: 成績:{student.grade}")
...
山田 高氏: 成績:優
小林 あゆみ: 成績:優
小野寺 佐由子: 成績:良
伊藤 朝子: 成績:良
寺田 淳子: 成績:秀
菊池 聡: 成績:不
原田 卓: 成績:良
桑原 真紀子: 成績:秀
三谷 景子: 成績:可
斎藤 景子: 成績:可
猿川 孝: 成績:秀
小池 裕也: 成績:良
小池 優子: 成績:秀
川口 健吾: 成績:優

次に、成績が「」以外の生徒の情報を取得します。

queryset = Student.objects.exclude(grade='秀')

取得した生徒の情報を確認します。

>>> for student in queryset:
...     print(f"{student.name}: 成績:{student.grade}")
...
山田 高氏: 成績:優
小林 あゆみ: 成績:優
小野寺 佐由子: 成績:良
伊藤 朝子: 成績:良
菊池 聡: 成績:不
原田 卓: 成績:良
三谷 景子: 成績:可
斎藤 景子: 成績:可
小池 裕也: 成績:良
川口 健吾: 成績:優

上記の通り、成績が「」以外のデータだけが取得できたことが確認できます。

valuesメソッド

valuesメソッドを使うと、QuerySetオブジェクトの代わりにPython辞書を返してくれます。

まずは普通に全データを取得して、戻り値のデータ型をチェックしてみましょう。


>>> queryset = Student.objects.values()

valuesメソッドを使うと戻り値を辞書型データとして扱えるので、以下のように辞書を扱う形でデータにアクセスできます。

以下は1番目の生徒データのみを取得する例です。

>>> queryset[0]
{'id': 2, 'name': '山田 高氏', 'grade': '優', 'school_id': 1, 'blood_group': 'O', 'mobile': '03-1234-1234', 'address': '滋賀県長浜市北池町543-17'}
>>> queryset[0]['name']
'山田 高氏'

また、特定の項目だけを取得したい場合はvaluesメソッドの引数に取得したい項目名を指定します。
以下は、名前(name)と成績(grade)だけ取得する例です。

>>> queryset = Student.objects.values("name", "grade")
>>> queryset
<QuerySet [{'name': '山田 高氏', 'grade': '優'}, {'name': '小林\u3000あゆみ', 'grade': '優'}, {'name': '小野寺\u3000 佐由子', 'grade': '良'}, {'name': '伊藤\u3000朝子', 'grade': '良'}, {'name': '寺田\u3000淳子', 'grade': '秀'}, {'name': '菊池\u3000聡', 'grade': '不'}, {'name': '原田\u3000卓', 'grade': '良'}, {'name': '桑原\u3000真紀子', 'grade': '秀'}, {'name': '三谷\u3000景子', 'grade': '可'}, {'name': '斎藤\u3000景子', 'grade': '可'}, {'name': '猿川\u3000孝', 'grade': '秀'}, {'name': '小池\u3000裕也', 'grade': '良'}, {'name': '小池\u3000優子', 'grade': '秀'}, {'name': '川口\u3000健吾', 'grade': '優'}]>

上記の通り、nameとgradeだけ取得できていることが確認できます。

values_listメソッド

values_listメソッドはvaluesメソッドに似ていますが、ディクショナリを返す代わりにタプルを返します。

>>> queryset = Student.objects.values_list('id', 'name')
>>> queryset
>>><QuerySet [(2, '山田 高氏'), (3, '小林\u3000あゆみ'), (4, '小野寺\u3000佐由子'), (5, '伊藤\u3000朝子'), (6, '寺田\u3000淳子'), (7, '菊池\u3000聡'), (8, '原田\u3000卓'), (9, '桑原\u3000真紀子'), (10, '三谷\u3000景子'), (11, '斎藤\u3000景子'), (12, '猿川\u3000孝'), (13, '小池\u3000裕也'), (14, '小池\u3000優子'), (15, '川口\u3000健吾')]>
>>> queryset[0]
(2, '山田 高氏')
>>> queryset[0][1]
'山田 高氏'

タプルの代わりにリストのような単一の値のみが必要な場合は、values_listメソッドに追加の引数flat = Trueを渡すことができます。
リストとして名前だけが必要な場合は以下のようなコードを実行します。

>>> queryset = Student.objects.values_list('name', flat=True)
>>> queryset
<QuerySet ['山田 高氏', '小林\u3000あゆみ', '小野寺\u3000佐由子', '伊藤\u3000朝子', '寺田\u3000淳子', '菊池\u3000聡', '原田\u3000卓', '桑原\u3000真紀子', '三谷\u3000景子', '斎藤\u3000景子', '猿川\u3000孝', '小池\u3000裕也', '小池\u3000優子', '川口\u3000健吾']>
>>> queryset[0]
'山田 高氏'
>>> queryset[1]
'小林\u3000あゆみ'
>>>

なお、value_listflat=Trueを使う場合は単一のフィールドでのみ機能します。
複数のフィールドを指定すると、エラーが発生します。

select_relatedメソッド

学生情報(Student)のテーブルはスクール情報(School)のテーブルと外部キーの関係があります。
学生情報からスクール情報を取得するには以下のようなコードを実行します。

>>> student = Student.objects.get(pk=2)
>>> student.school
<School: 東京大学>
>>> student.school.name
'東京大学'

まず、学生情報を取得するためにStudentテーブルに対して検索クエリを実行します。
次に、取得した学生情報のスクール情報を取得するために追加のデータベース検索(Schoolテーブルに対する検索)を実行します。

つまり、これは2回の検索クエリが実行されるわけです。

上記の例ではデータが1つだけなので問題になりませんが、外部キーの関係が膨大な大規模データベースの場合はパフォーマンスが悪化する可能性があります。

select_relatedメソッドを使用すると、データベースに最初にアクセスしたときにすべての関連するテーブルへの検索が実行されることで、データベースのパフォーマンスを向上させることができます。

先ほどと同じ例で、select_relatedメソッドを使うと以下のようになります。

>>> student = Student.objects.select_related('school').get(pk=2)
>>> student.school.name
'東京大学'

上記の場合、1行目のコマンドを実行した時点でschoolの情報も一緒にstudent変数に格納されています。

つまり2行目のコマンドを実行した時点では既にデータベースからスクール情報は取得済みのため、2行目ではデータベースに対するクエリは実行されず、取得済みのstudent変数から情報を取得するだけです。

order_byメソッド

order_byメソッドは、取得結果をソートしたい場合に利用します。
デフォルトの検索順序は主キー(id)フィールドに基づきます。

例えば、QuerySetを成績順(grade)に並べたい場合はgradeフィールドをorder_by()メソッドに指定します。

>>> Student.objects.order_by("grade")
<QuerySet [<Student: 菊池 聡>, <Student: 山田 高氏>, <Student: 小林 あゆみ>, <Student: 川口 健吾>, <Student: 三谷  景子>, <Student: 斎藤 景子>, <Student: 寺田 淳子>, <Student: 桑原 真紀子>, <Student: 猿川 孝>, <Student: 小池 優子>, <Student: 小野寺 佐由子>, <Student: 伊藤 朝子>, <Student: 原田 卓>, <Student: 小池 裕也>]>
>>> queryset = Student.objects.order_by("grade")
>>> for student in queryset:
...     print(f"{student.name}:成績:{student.grade}")
...
菊池 聡:成績:不
山田 高氏:成績:優
小林 あゆみ:成績:優
川口 健吾:成績:優
三谷 景子:成績:可
斎藤 景子:成績:可
寺田 淳子:成績:秀
桑原 真紀子:成績:秀
猿川 孝:成績:秀
小池 優子:成績:秀
小野寺 佐由子:成績:良
伊藤 朝子:成績:良
原田 卓:成績:良
小池 裕也:成績:良

上記の通り成績毎に生徒の情報が取得できていることが確認できます。

もし降順で表示したい場合は、次のようにorder_byの引数で指定するカラム名の前に「-」をつけます。

>>> queryset = Student.objects.order_by("-grade")
>>> for student in queryset:
...     print(f"{student.name}:成績:{student.grade}")
...
小野寺 佐由子:成績:良
伊藤 朝子:成績:良
原田 卓:成績:良
小池 裕也:成績:良
寺田 淳子:成績:秀
桑原 真紀子:成績:秀
猿川 孝:成績:秀
小池 優子:成績:秀
三谷 景子:成績:可
斎藤 景子:成績:可
山田 高氏:成績:優
小林 あゆみ:成績:優
川口 健吾:成績:優
菊池 聡:成績:不

existsメソッド

existsメソッドは返されたQuerySetに1つ以上のオブジェクトが含まれている場合はTrueを、QuerySetが空の場合はFalseを返します。
生徒の名前が存在するかどうか確認するには以下のように実行します。

>>> Student.objects.filter(name='小池 優子').exists()
True
>>> Student.objects.filter(name='小池 裕子').exists()
False

countメソッド

countメソッドは、QuerySet内のエントリの数をカウントします。
生徒のデータが何件存在するか確認するには以下のコードを実行します。

>>> Student.objects.count()
14

次の例は、成績が「」の生徒数を確認するコードです。

>>> Student.objects.filter(grade="優").count()
3

firstメソッドとlastメソッド

firstメソッドは、QuerySetの最初の要素を返します。
lastメソッドは、、QuerySetの最後の要素を返します。

まず、全生徒の情報を確認します。

>>> Student.objects.all()
<QuerySet [<Student: 山田 高氏>, <Student: 小林 あゆみ>, <Student: 小野寺 佐由子>, <Student: 伊藤 朝子>, <Student: 寺田 淳子>, <Student: 菊池 聡>, <Student: 原田 卓>, <Student: 桑原 真紀子>, <Student: 三谷 景子>, <Student: 斎 藤 景子>, <Student: 猿川 孝>, <Student: 小池 裕也>, <Student: 小池 優子>, <Student: 川口 健吾>]>

firstメソッドで最初の要素を取得します。

>>> Student.objects.all().first()
<Student: 山田 高氏>

lastメソッドで最後の要素を取得します。

>>> Student.objects.all().last()
<Student: 川口 健吾>

in_bulkメソッド

in_bulkメソッドは引数に主キー(ID値)のリストを指定します。

戻り値は、引数に指定したIDをキーとした辞書型のデータを返します。
リストをin_bulk()メソッドに渡さないと、すべてのオブジェクトが返されます。

以下は、ID値が2と4のStudentのデータをin_bulkで取得する例です。

>>> students = Student.objects.in_bulk([2,4])
>>> students
{2: <Student: 山田 高氏>, 4: <Student: 小野寺 佐由子>}

studentsには辞書型のデータが可能されています。
例えばID=2の生徒名を取得したい場合は以下のように実行します。

>>> students[2].name
'山田 高氏'

explainメソッド

このメソッドは、QuerySetの実行プランを返します。
使用されているインデックスや結合など、データベースがクエリをどのように実行しているのか、詳細な情報を確認することができます。

>>> Student.objects.filter(pk=2).explain()
'2 0 0 SEARCH TABLE app_student USING INTEGER PRIMARY KEY (rowid=?)'

以上でこのレシピは完了です。

主にITテクノロジー系に興味があります。 【現在興味があるもの】 python、Django,統計学、機械学習、ディープラーニングなど。 技術系ブログもやってます。 https://sinyblog.com/