LoginSignup
48
51

[初心者]Django Query データベースから取得

Last updated at Posted at 2020-09-20

Djangoでfilterメソッド、getなどDBからデータを取得するときに書き方忘れるのでまとめます。

では、今回はQueryを取得する方法を初心者向けの勉強用として書きます
簡単な例も付けますので参考に。

続き:リレーション
:heavy_check_mark: [初心者] #2 Django Query データベース取得 1対多と多対多

過去記事: データベースから取得してレンダリング

image.png

環境

  • 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個ずつデータ取り出さないくても使えるんだな」

.python
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='条件入力')
:white_check_mark: 例:nameSatoのデータ取得

customers = Customer.objects.filter(name='Sato')

nameSatoに一致するデータを取得

2.2.1 AND条件

複数の条件付ける。単純にカンマで繋げるだけ
モデル名.objects.filter(field1='条件入力1', field2='条件入力2')

:white_check_mark: 例:nameSatoage50のデータ取得

customers = Customer.objects.filter(name='Sato', age=50)

2.2.2 OR条件

ORが少しややこしい。from django.db.models import Q というのを使って|をつける必要ある。

:white_check_mark: 例:nameSatoもしくはage50のデータ取得

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=[リストデータ])

:white_check_mark: 例:nameSato, 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の略)を付ける。
:white_check_mark: 例:「年齢が20より大きい」(age > 20)なら

customers = Customer.objects.filter(age__gt=20)

以上(greater than equal)

モデル名.objects.filter(field__gte='条件')
_(アンダーバー)が2つとgte(greater than equalの略)を付ける。
:white_check_mark: 例:「年齢が20以上」(age >= 20)なら

customers = Customer.objects.filter(age__gte=20)

未満(less than)

モデル名.objects.filter(field__lt='条件')
_(アンダーバー)が2つとlt(greater thanの略)。
:white_check_mark: 例:「年齢が10未満」(age < 10)なら

customers = Customer.objects.filter(age__lt=10)

以下(less than equal)

モデル名.objects.filter(field__lte='条件')
_(アンダーバー)が2つとlte(greater than equalの略)。
:white_check_mark: 例:「年齢が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))

:white_check_mark: 例: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='条件')

:white_check_mark: 例:saを名前に含んでいたら取得

customers = Customer.objects.filter(name__contains='sa')

大文字小文字区別無し

_(アンダーバー)が2つとicontains
モデル名.objects.filter(field__icontains='条件')

:white_check_mark: 例:SaでもSAでもsaでもいいので名前に含んでいたら取得

customers = Customer.objects.filter(name__contains='sa')

前方一致

文字小文字区別する

大文字小文字区別あり

_(アンダーバー)が2つとstartwith
モデル名.objects.filter(name__startwith='T')
:white_check_mark: 例:Tから始まる名前取得

customers = Customer.objects.filter(name__startwith='T')

大文字小文字区別なし

_(アンダーバー)が2つとistartwith
モデル名.objects.filter(name__istartwith='T')
:white_check_mark: 例:TA, ta, Ta, tAから始まる名前取得

customers = Customer.objects.filter(name__istartwith='Ta')

後方一致

文字小文字区別する

大文字小文字区別あり

_(アンダーバー)が2つとendwith
モデル名.objects.filter(name__endwith='a')
:white_check_mark: 例:aで終わる名前取得

customers = Customer.objects.filter(name__endwith='a')

大文字小文字区別なし

_(アンダーバー)が2つとiendswith
モデル名.objects.filter(name__iendswith='ka')
:white_check_mark: 例:KA, ka, Ka, kAで終わる名前取得

customers = Customer.objects.filter(name__iendswith='ka')

完全一致(大文字小文字区別しない)

_(アンダーバー)が2つとiexact

モデル名.objects.filter(field__iexact='条件')

:white_check_mark: 例: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を除外

:white_check_mark: 例: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の条件などで使うとき便利。データがなかったら「データないです」表示とかの判定に便利。

:white_check_mark: 例:10歳以下のデータあったらTrueなので条件の中を処理

.python
if Customer.objects.filter(age__lte=10).exists()
    pass

2.2.11 exclude: 条件を除外して取得

除外。つまりfilterの逆で、条件反転ということ。

:white_check_mark: 例:10歳以下のデータ以外を取得

.python
customers = Customer.objects.exclude(age__lte=10)

2.2.12 first:querysetの最初のデータを取得

:white_check_mark: 例:10歳以下のデータで一番最初のデータ取得

.python
customers = Customer.objects.filter(age__lte=10).order_by('created_at').first()

以下と同じような意味。

.python
customers = Customer.objects.filter(age__lte=10).order_by('created_at')[0]

2.2.13 last:querysetの最後のデータ取得

firstと逆です。

.python
customers = Customer.objects.filter(age__lte=10).order_by('created_at').last()

以下と同じような意味。order_byの順序変わっただけです。

.python
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としています。

accounts/models.py
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を編集

アプリケーションをプロジェクトに追加します。

django_docker/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に登録しないと表示されないので。

django_docker/accounts/admin.py
from django.contrib import admin
from .models import Customer

admin.site.register(Customer)

http://127.0.0.1:8000/adminにアクセスしてログイン。
スクリーンショット 2020-09-21 0.35.07.png

「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使うといいです。

.python
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/

今後も随時追加していきます。

続き:リレーション

[初心者] #2 Django Query データベース取得 1対多と多対多

48
51
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
48
51