Djangoでのデータベースの扱い方

f:id:monozukuri-bu:20190708221030j:plain

Oracle Master 12c Bronze SQL基礎の資格取得に向け日々精進しているhashiです。

今回は自分が過去に扱ったDjango(ジャンゴ)というPythonのWebアプリケーションフレームワークでのデータベース操作を行う主なクエリと、それと等価なSQL文を併記して、知識を整理することを目的とします。

動作環境

Python 3.7.3
Django 2.2.1

テーブルの生成

Djangoでのテーブル定義は以下のように記述します。
クラス名がテーブル名、クラス変数の左辺が列の名前、右辺に型の情報やオプションを記述しています。

"""models.py"""
from django.db import models


class UserTable(models.Model):
    """ユーザー情報のテーブル"""
    user_id = models.IntegerField("ID", primary_key=True)
    name = models.CharField("ユーザー名", max_length=30)
    age = models.IntegerField("年齢", blank=True, null=True)


class PhoneTable(models.Model):
    """ユーザーの持つ電話のテーブル"""
    user = models.ForeignKey(UserTable, on_delete=models.CASCADE)
    tel = models.CharField("電話番号", max_length=15)
    email = models.EmailField("メールアドレス")
    contract_date = models.DateField("契約日", blank=True, null=True)

上のように記述した後、「データベースマイグレーション」という操作を行うとデータベース上にテーブルが作られます。

vsCode拡張機能であるSQLiteを用いてデータベースの情報を確認すると……

UserTable

NULLを許容する 制約
user_id integer NOT NULL PRIMARY KEY
name varchar(30) NOT NULL
age integer

PhoneTable

NULLを許容する 制約
user integer NOT NULL REFERENCES
tel varchar(15) NOT NULL
email varchar(254) NOT NULL
contract_date date

このように、ユーザー情報と携帯電話の情報という2つのテーブルが作られます。

テーブルからのオブジェクトの取得

Djangoでは、データベースからオブジェクトを取得する際に「クエリセット」を作ります。
Djangoのドキュメント(https://docs.djangoproject.com/ja/2.2/topics/db/queries/)によれば、

データベースからオブジェクトを取得するには、モデルクラスの Manager から QuerySet を作ります。 QuerySet はデータベース上のオブジェクトの集合を表しています。多数の フィルター を持つことができます。フィルターは与えられたパラメータに基づいてクエリの検索結果を絞り込みます。SQL 文においては、 QuerySet は SELECT 句、フィルターは WHERE や LIMIT のような絞り込みに用いる句に対応しています。

とあります。実際に絞り込みを行ってみます。
先ほど作ったテーブルに以下のようなデータを追加しました。
これからの操作はこのデータを用いて行います。

UserTable

user_id name age
1 tanaka 34
2 sato
3 suzuki 40
4 sasaki 24

PhoneTable

user TEL email contract_date
1 0120-456-456 tanaka@t.com 2018年10月1日
1 090-1234-5678 tanaka2@t.com 2019年8月1日
2 0120-999-999 test@t.com 2019年7月4日
3 0120-202-102 temp@temp.com 2018年11月7日
2 0120-68-1147 phone@t.com 2019年7月23日
1 0120-976-543 testtest@t.com
1 0120-456-456 karikari@test.com
4 456 poi@poi.jp

全データの取得

phones0 = PhoneTable.objects.all() #全データの取得

PhoneTableクラスのManagerであるobjectsにall() メソッドを呼ぶことで全データを取得できます。
SQL文で表すとこのようになるでしょう。

SELECT * FROM PhoneTable;

取得したデータ

user TEL email contract_date
1 0120-456-456 tanaka@t.com 2018年10月1日
1 090-1234-5678 tanaka2@t.com 2019年8月1日
2 0120-999-999 test@t.com 2019年7月4日
3 0120-202-102 temp@temp.com 2018年11月7日
2 0120-68-1147 phone@t.com 2019年7月23日
1 0120-976-543 testtest@t.com
1 0120-456-456 karikari@test.com
4 456 poi@poi.jp

完全一致検索

取得するオブジェクトの絞り込みを行う際はfilter()メソッドを利用します。引数に入れる条件を変えることで検索条件を変えることができ、

phones1 = PhoneTable.objects.filter(tel='0120-456-456')  # 完全一致検索

列名=値を引数にすることでPhoneTableのtel列が0120-456-456に等しいデータのみを取得できます。
SQL文で表すとこのようになるでしょう。

SELECT * FROM PhoneTable WHERE tel='0120-456-456';

取得したデータ

user TEL email contract_date
1 0120-456-456 tanaka@t.com 2018年10月1日
1 0120-456-456 karikari@test.com

部分一致検索

完全一致だけでなく部分一致も可能です。

phones2 = PhoneTable.objects.filter(tel__contains='456')  # 部分一致検索

列名__contains=値を引数にすることでPhoneTableのtel列に456を含むデータのみを取得できます。
SQL文で表すとこのようになるでしょう。

SELECT * FROM PhoneTable WHERE tel LIKE '%456%';

取得したデータ

user TEL email contract_date
1 0120-456-456 tanaka@t.com 2018年10月1日
1 0120-456-456 karikari@test.com
4 456 poi@poi.jp

リスト中からの検索

複数の値のどれかと等しいデータの取得が可能です。

# リスト中からの検索
phones3 = PhoneTable.objects.filter(tel__in=['0120-456-456', '0120-999-999'])

列名__in=[値1,値2…]を引数にすることでPhoneTableのtel列がリストのどれかと等しいデータを取得できます。
SQL文で表すとこのようになるでしょう。

SELECT * FROM PhoneTable WHERE tel IN ('0120-456-456', '0120-999-999');

取得したデータ

user TEL email contract_date
1 0120-456-456 tanaka@t.com 2018年10月1日
2 0120-999-999 test@t.com 2019年7月4日
1 0120-456-456 karikari@test.com

○○未満のものを検索

# 比較対象未満(日付なら過去)を検索
phones4 = PhoneTable.objects.filter(contract_date__lt=datetime.date.today())

列名__lt=値を引数にすることでPhoneTableのcontract_date列がdatetime.date.today()(=2019年7月5日)より前のデータのみを取得できます。(NULLとの比較はできない)
SQL文で表すとこのようになるでしょう。

SELECT * FROM PhoneTable WHERE contract_date < SYSDATE;

取得したデータ

user TEL email contract_date
1 0120-456-456 tanaka@t.com 2018年10月1日
2 0120-999-999 test@t.com 2019年7月4日
3 0120-202-102 temp@temp.com 2018年11月7日

○○より大きいものを検索

# 比較対象より大きいもの(日付なら未来)を検索
phones5 = PhoneTable.objects.filter(contract_date__gt=datetime.date.today())

列名__gt=値を引数にすることでPhoneTableのcontract_date列がdatetime.date.today()(=2019年7月5日)より後のデータのみを取得できます。
SQL文で表すとこのようになるでしょう。

SELECT * FROM PhoneTable WHERE contract_date > SYSDATE;

取得したデータ

user TEL email contract_date
1 090-1234-5678 tanaka2@t.com 2019年8月1日
2 0120-68-1147 phone@t.com 2019年7月23日

NULL判定

# NULLの判定
phones6 = PhoneTable.objects.filter(contract_date__isnull=True)

列名__isnull=Trueを引数にすることでPhoneTableのcontract_date列がNULLのデータのみを取得できます。
SQL文で表すとこのようになるでしょう。

SELECT * FROM PhoneTable WHERE contract_date IS NULL;

取得したデータ

user TEL email contract_date
1 0120-976-543 testtest@t.com
1 0120-456-456 karikari@test.com
4 456 poi@poi.jp

外部キーで検索

# 外部キーで検索
temp = UserTable.objects.get(age=34)
phones7 = PhoneTable.objects.filter(user=temp)

外部キーを条件とした検索も可能です。上では2段階に分けて検索を行っています。
1行目でget()を使用している理由ですが、filter()では検索結果が1つの場合でも返り値の型がリストになってしまうためです。
SQL文で表すとこのようになるでしょう。

SELECT * FROM PhoneTable WHERE (SELECT user_id FROM UserTable WHERE age = 34) = 1;

取得したデータ

user TEL email contract_date
1 0120-456-456 tanaka@t.com 2018年10月1日
1 090-1234-5678 tanaka2@t.com 2019年8月1日
1 0120-976-543 testtest@t.com
1 0120-456-456 karikari@test.com

and検索

# and検索
phones8 = PhoneTable.objects.filter(tel="0120-999-999", email="test@t.com")

filter()の引数に複数の検索条件をカンマで区切って入れることで、そのすべてに当てはまるデータを取得できます。
SQL文で表すとこのようになるでしょう。

SELECT * FROM PhoneTable WHERE tel='0120-999-999' AND email='test@t.com';

取得したデータ

user TEL email contract_date
2 0120-999-999 test@t.com 2019年7月4日

or検索

from django.db.models import Q
# or検索
phones9 = PhoneTable.objects.filter(Q(tel="0120-999-999") | Q(email="phone@t.com"))

or検索は少し特殊で、Qオブジェクトというものを使って実現しています。Q(条件1) | Q(条件2)という形式になります。 SQL文で表すとこのようになるでしょう。

SELECT * FROM PhoneTable WHERE tel='0120-999-999' OR email='phone@t.com';

取得したデータ

user TEL email contract_date
2 0120-999-999 test@t.com 2019年7月4日
2 0120-68-1147 phone@t.com 2019年7月23日

まとめ

Djangoにおける主要なデータベースからのデータ取得方法と、それに対応するSQL文についてまとめました。
今後はINSERTやUPDATE関連もまとめたいと思います。