Djangoでfilterメソッド、getなどDBからデータを取得するときに書き方忘れるのでまとめます。
では、今回はQueryを取得する方法を初心者向けの勉強用として書きます
簡単な例も付けますので参考に。
続き:リレーション
[初心者] #2 Django Query データベース取得 1対多と多対多
過去記事: データベースから取得してレンダリング
環境
- Python 3.8.5
- Django 3.1
データは補足編に書いたようなデータモデルで環境を作って実行しました。
1. get: データ1つ取得
filter
と似てますが、違いあり。補足にも書きました。getとfilterの違い
- 単体のオブジェクト取得
- 1件も取得できないと例外発生
- 即座にDBから取得
モデル名.objects.get(field='条件入力')
Customer.objects.get(id=1)
プライマリーキー(idとか)ならpk
を使える。
モデル名.objects.get(pk='条件入力')
Customer.objects.get(pk=1)
取得できる結果は同じ。
いまいちわからない方は、
「データ1つ必ず取れるときに使うんだな」
「QuerySetと違って、リスト的なものに入ってないから ↓ みたいに1個ずつデータ取り出さないくても使えるんだな」
customer = Customer.objects.get(pk=customer_id)
if(customer.age < 20):
pass
くらい雑に覚えておいてもいいかもしれません。
ついでにfilterとget組み合わせることもできる
補足 filterで一つしかデータない場合はget()使える
2. QuerySet: 複数データ取得
get
と違いあり。補足にも書きました。getとfilterの違い
- 複数のオブジェクト取得
- 0件取得でもエラー出ない。カラのデータということになる
- 即座に取得しない。
for
,print
などなどデータ見ないと処理できないような特定の処理されたときにDBに問い合わせる。>> # 慣れてきたら知っておくといいこと
まあ、いまいちわからない方はとりあえず、
「QuerySetはリストみたいなのに入って取得するんだな」
「.get()
と違ってデータを取得できなくてもエラーならない。助かるときある」
くらい雑に覚えておいてもいいかもしれません。
2.1 all:データすべて取得する
テーブルのデータすべて取得したいってときに。
customers = Customer.objects.all()
複数のオブジェクト(QuerySet)で取得されます。
SQLならSELECT * FROM oooo;
やっているということ。実際の開発の時にはあまり使わないかもしれません。
2.2 QuerySetのfilterでのデータ取得まとめ
複数のオブジェクト(QuerySet)を取得したいけど条件付けるときによく使います。
完全一致
モデル名.objects.filter(field='条件入力')
例:name
がSato
のデータ取得
customers = Customer.objects.filter(name='Sato')
name
がSato
に一致するデータを取得
2.2.1 AND条件
複数の条件付ける。単純にカンマで繋げるだけ
モデル名.objects.filter(field1='条件入力1', field2='条件入力2')
例:name
がSato
、age
が50
のデータ取得
customers = Customer.objects.filter(name='Sato', age=50)
2.2.2 OR条件
ORが少しややこしい。from django.db.models import Q
というのを使って|
をつける必要ある。
例:name
がSato
もしくはage
が50
のデータ取得
from django.db.models import Q
customers = Customer.objects.filter(Q(name='Sato') | Q(age=50))
2.2.3 IN句 (where in)
filterに条件をつけまくる方法もあるけど、条件をリストで渡して条件に一致するデータを取得できてとてもスマートに書ける。
モデル名.objects.filter(field__in=[リストデータ])
例:name
がSato, Takahashi, Tanaka
の、どれかだったら取得
customers = Customer.objects.filter(name__in=['Sato', 'Takahashi', 'Tanaka'])
リストを用意して与えてやれば作れる。便利。
2.2.4 不等号使った条件(「>」「<」「>=」「<=」)
不等号は以下の文字にて条件を作る。
意味 | |
---|---|
gt | (greater than) 超 |
gte | (greater than equal) 以上 |
lt | (less than) 未満 |
lte | (less than equal) 以下 |
より大きい・超える(greater than)
モデル名.objects.filter(field__gt='条件')
のように_
(アンダーバー)が2つとgt
(greater thanの略)を付ける。
例:「年齢が20より大きい」(age > 20
)なら
customers = Customer.objects.filter(age__gt=20)
以上(greater than equal)
モデル名.objects.filter(field__gte='条件')
_
(アンダーバー)が2つとgte
(greater than equalの略)を付ける。
例:「年齢が20以上」(age >= 20
)なら
customers = Customer.objects.filter(age__gte=20)
未満(less than)
モデル名.objects.filter(field__lt='条件')
_
(アンダーバー)が2つとlt
(greater thanの略)。
例:「年齢が10未満」(age < 10
)なら
customers = Customer.objects.filter(age__lt=10)
以下(less than equal)
モデル名.objects.filter(field__lte='条件')
_
(アンダーバー)が2つとlte
(greater than equalの略)。
例:「年齢が10以下」(age <= 10
)なら
customers = Customer.objects.filter(age__lte=10)
2.2.5 BETWEEN句
モデル名.objects.filter(field__range=(start, end))
_
(アンダーバー)が2つとrange
数値の範囲や日時期間をstartとendを指定。
Sample.objects.filter(field__range=(start, end))
例:SQLにて、age BETWEEN 0 AND 20
つまり、0 <= age AND age <= 20
customers = Customer.objects.filter(age__range=(0, 20))
2.2.6 LIKE句
LIKEというのはなくて、_
(アンダーバー)が2つと文字を付ける
部分一致
大文字小文字区別あるので注意。
大文字小文字区別あり
_
(アンダーバー)が2つとcontains
モデル名.objects.filter(field__contains='条件')
例:saを名前に含んでいたら取得
customers = Customer.objects.filter(name__contains='sa')
大文字小文字区別無し
_
(アンダーバー)が2つとicontains
モデル名.objects.filter(field__icontains='条件')
例:SaでもSAでもsaでもいいので名前に含んでいたら取得
customers = Customer.objects.filter(name__contains='sa')
前方一致
文字小文字区別する
大文字小文字区別あり
_
(アンダーバー)が2つとstartwith
モデル名.objects.filter(name__startwith='T')
例:Tから始まる名前取得
customers = Customer.objects.filter(name__startwith='T')
大文字小文字区別なし
_
(アンダーバー)が2つとistartwith
モデル名.objects.filter(name__istartwith='T')
例:TA, ta, Ta, tAから始まる名前取得
customers = Customer.objects.filter(name__istartwith='Ta')
後方一致
文字小文字区別する
大文字小文字区別あり
_
(アンダーバー)が2つとendwith
モデル名.objects.filter(name__endwith='a')
例:aで終わる名前取得
customers = Customer.objects.filter(name__endwith='a')
大文字小文字区別なし
_
(アンダーバー)が2つとiendswith
モデル名.objects.filter(name__iendswith='ka')
例:KA, ka, Ka, kAで終わる名前取得
customers = Customer.objects.filter(name__iendswith='ka')
完全一致(大文字小文字区別しない)
_
(アンダーバー)が2つとiexact
モデル名.objects.filter(field__iexact='条件')
例:SatoでもSATOでもsatoでもいいので名前で一致したら取得
customers = Customer.objects.filter(neme__iexact='sato')
2.2.7 NULLを検索(IS NULL)、除外(IS NOT NULL)
_
(アンダーバー)が2つとisnull
モデル名.objects.filter(field__isnull=True)
これでNULLを取得。
_
(アンダーバー)が2つとisnull
モデル名.objects.filter(field__isnull=False)
これで、NULLを除外
例:ageがNULLのデータ取得
customers = Customer.objects.filter(age__isnull=True)
2.2.8 order_by: データ並べ替え
querysetのデータにくっつけてデータを並べ替えることが可能。SQLのorder_byと同じような感じで使えるのでわかりやすいと思う。
Customer.objects.all().order_by('id') # idカラム昇順で並べ替え
Customer.objects.all().order_by('-id') # idカラム降順で並べ替え
Customer.objects.all().order_by('id', 'age') # idを並べ替えた後にageでも
デフォルトが昇順で、-
を付けると降順にできます。
複数書くと順々に並べ替えてくれる。
もう少し詳しく並び替える場合は公式ページ参照。
https://docs.djangoproject.com/ja/3.2/ref/models/querysets/#django.db.models.query.QuerySet.order_by
2.2.9 count:データ数取得
データの個数を取得できる。
↓のような組み込み関数のcount()は使わないでQuerysetのcount使いましょう。
customers = Customer.objects.all()
count = count(customers)
これはやめて↓使いましょう。
count = Customer.objects.all().count()
2.2.10 exists:存在するか?(True or Falseでとれる)
ifの条件などで使うとき便利。データがなかったら「データないです」表示とかの判定に便利。
例:10歳以下のデータあったらTrueなので条件の中を処理
if Customer.objects.filter(age__lte=10).exists()
pass
2.2.11 exclude: 条件を除外して取得
除外。つまりfilterの逆で、条件反転ということ。
例:10歳以下のデータ以外を取得
customers = Customer.objects.exclude(age__lte=10)
2.2.12 first:querysetの最初のデータを取得
例:10歳以下のデータで一番最初のデータ取得
customers = Customer.objects.filter(age__lte=10).order_by('created_at').first()
以下と同じような意味。
customers = Customer.objects.filter(age__lte=10).order_by('created_at')[0]
2.2.13 last:querysetの最後のデータ取得
firstと逆です。
customers = Customer.objects.filter(age__lte=10).order_by('created_at').last()
以下と同じような意味。order_by
の順序変わっただけです。
customers = Customer.objects.filter(age__lte=10).order_by('-created_at')[0]
2.2.14 limit: 取得数を指定
モデル名.objects.all()[:5] # 5件取得(Limit 5)
モデル名.objects.all()[5:10] # 5~10件を取得(OFFSET 5 LIMIT 5)
2.2.15 いろんな条件を重ねること可能
countとexistsなどでもつなげていましたが、検索条件や並べ替えなどなどぺたぺたくっつけることができます。
Customer.objects.all().order_by('id') # idカラム昇順で並べ替え
Customer.objects.all().order_by('-id') # idカラム降順で並べ替え
Customer.objects.all().filter(name='Sato').reverse() # 佐藤さん取得。Querysetデータを逆にして取得
Customer.objects.all().filter(name='Sato').count() # 佐藤さんの数カウント
Customer.objects.values('email').distinct() # emailカラムの重複データは除去
querysetは変数に入れてくっつけていくこともできる
queryset = Customer.objects.filter(name='Sato')
queryset = queryset.filter(age__lte=10)
if
など使えば条件を動的につけていくことができます。
2.3 取得データを加工・別形式にて
values:dictionary(辞書型)で取得したい
DBから取得したときに辞書型で取得できると加工しやすいことなどあります。
参考: https://docs.djangoproject.com/ja/3.1/ref/models/querysets/#values
>>> Blog.objects.filter(name__startswith='Beatles').values()
<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>
普通に取得するとQuerySetをforで回して取り出してもdictinaryでなので加工しにくいのですが、上記のようにvalue
をつけてやるとレコードの内容をdictinaryで取得できるって感じで使えます。
values_list:タプルで取得(キーが邪魔なら)
values_list
はvaluesに似てるけど、値だけ返します。
参考:https://docs.djangoproject.com/ja/3.1/ref/models/querysets/#values-list
分析とか、CSVに書き出すとか、ライブラリ使うときlistで渡す必要あるとか、キーがついてるのが邪魔というときに。
>>> Entry.objects.values_list('id', 'headline')
<QuerySet [(1, 'First entry'), ...]>
>>> from django.db.models.functions import Lower
>>> Entry.objects.values_list('id', Lower('headline'))
<QuerySet [(1, 'first entry'), ...]>
条件に当てはまるID(プライマリーキー)の数字をリストでとりたいって時にはflat=True
を指定すると便利。
QuerySetでなく、単純なリストにしたいときはlist()
を使うと良い。
>>> Entry.objects.all().values_list('pk', flat=True))
<QuerySet [1, 2, ...]>
# リストにする all()をfilter()にすれば条件絞り込み可能
>>> list(Entry.objects.all().values_list('pk', flat=True)))
[1, 2, ...]
補足編
以下は補足です。試しにDBからのデータ取得練習したいという場合に活用してください。
DjangoでCRUDするならshellで確認してみる
いちいち確認するためにページに表示させるとか面倒なのでshellを使ってみましょう。
ついでに、shell_plus
というのをインストールすると便利なので使ってもいいと思います。
準備
今回使うmodelはこんな感じ
アプリ名はaccountsとしています。
class Customer(models.Model):
name = models.CharField(max_length=100)
phone = models.CharField(max_length=20)
email = models.CharField(max_length=255)
age = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
練習用のアプリ作成&データの入れかた
勉強用に今回作っていきます。
※manage.pyにいろんな機能が記述されています。manage.py使う時はディレクトリに移動して使ってください
$ django-admin startproject django_docker
$ cd django_docker
$ python manage.py startapp accounts
settings.pyを編集
アプリケーションをプロジェクトに追加します。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'accounts', # 追加
]
先ほどのmodels.pyのコードを書いて
$ python manage.py makemigrations
$ python manage.py migrate
データがすでにあるなら次へ
データないなら、
のようにfixtureで初期データ入れてもいいともいます。もしくは管理画面で登録。
$ python manage.py createsuperuser
で管理画面作ったら適当にユーザー情報をsuperuserを入れて作る。
admin.pyに登録しないと表示されないので。
from django.contrib import admin
from .models import Customer
admin.site.register(Customer)
http://127.0.0.1:8000/admin
にアクセスしてログイン。
「ADD CUSTOMER」で追加できます。
Djangoのデータをshellで取得してテスト
$ python manage.py shell
モデルを読み込まないと使えないのでimportしてください。
>>> from accounts.models import *
>>> customer = Customer.objects.all()
>>> customer
<QuerySet [<Customer: Sato>, <Customer: Tanaka>]>
こんな感じで取得することができます。
getとfilter
get()でデータ取得できないとエラーDoesNotExist
を返す。
filterでデータ取得できないと空のQuerysetを返す。
例をみてみます。
>>> customer_filter = Customer.objects.filter(id=1)
>>> customer_filter
<QuerySet [<Customer: Sato>]>
>>> customer_get = Customer.objects.get(id=1)
>>> customer_get
<Customer: Sato>
QuerySetというのが、なんというか、配列というか、レコードのまとまりというか。
filter
で取得したらforで取り出す必要あります。
customer_filter.name
としたらエラーです。
customer_get.name
はちゃんと処理されます。雑にいうとgetで取得できるようなデータを一つにまとめて何個も何個も入っているのがQuerySetですね。
だから、forで一つずつ取り出せば同じように扱えますね。
>>> customer_filter = Customer.objects.filter(id=1)
>>> customer_filter.name
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'QuerySet' object has no attribute 'name'
>>> customer_filter = Customer.objects.filter(id=1)
>>> for c in customer_filter:
... c.name
...
'Sato'
getのDoesNotExist
確実データが取得できるとき、取得できない場合処理がおかしくなるから例外(DoesNotExist)を発生させてほしい場合にget
使うといいです。
try:
user = User.objects.get(pk=id)
# ここに何か書く
except User.DoesNotExist:
pass
慣れてきたら知っておくといいこと
QuerySets作ってるだけならデータベースにアクセスしてません。
>>> q = accounts.objects.filter(name__startswith="T")
>>> q = q.filter(created_at__lte=datetime.date.today())
>>> q = q.exclude(age__gte=30)
>>> print(q)
3回DBにアクセスしているように見えるけど、実際は最後のprint(q)
の一回だけです。
開発が小規模の内は、だからどうしたとなるかもしれませんが、何回も重たい処理をDBに投げるが気になるので書きました。
filterで一つしかデータない場合はget()使える
例えば以下のような条件の場合当たり前ですが、取得できるのは一つだけです。これを取り出して、、、なんてのはちょっと冗長。
customer = Customer.objects.filter(id=1)
print(customer[0])
get()
使おう。利点としては
- 1行でスッキリ一つのデータ取り出せる
- データが2つ存在したらエラー出してくれる
- なかったらエラー出してくれる
customer = Customer.objects.filter(id=1).get()
print(customer)
# データ二つなのでエラー出る
customer = Customer.objects.filter(id__in=[1,2]).get()
apps.customers.models.Contract.MultipleObjectsReturned: get() returned more than one Contract -- it returned 2!
参考
https://docs.djangoproject.com/ja/3.1/topics/db/queries/
https://docs.djangoproject.com/ja/3.1/ref/models/querysets/