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

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

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


続き：リレーション
[:heavy_check_mark: [初心者] #2 Django Query データベース取得 1対多と多対多](https://qiita.com/Bashi50/items/2cdff211c45e54a4c17c)

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

https://qiita.com/Bashi50/items/280bc341eeec5d7f8797

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/71d6f566-f964-484d-c166-6aad2063a9d7.png)


# 環境
- Python 3.8.5
- Django 3.1

データは[補足編](#補足編)に書いたようなデータモデルで環境を作って実行しました。

# 1. get： データ1つ取得
`filter`と似てますが、違いあり。[補足にも書きました。getと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()使える](#filterで一つしかデータない場合はget使える)

# 2. QuerySet: 複数データ取得
`get`と違いあり。[補足にも書きました。getとfilterの違い](#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:　例：`name`が`Sato`のデータ取得

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

`name`が`Sato`に一致するデータを取得

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

:white_check_mark:　例：`name`が`Sato`、`age`が`50`のデータ取得

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

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

:white_check_mark:　例：`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=[リストデータ])`

:white_check_mark:　例：`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の略)を付ける。
: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と同じような感じで使えるのでわかりやすいと思う。

```python
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: 取得数を指定

```python
モデル名.objects.all()[:5] # 5件取得(Limit 5)
モデル名.objects.all()[5:10] # 5~10件を取得(OFFSET 5 LIMIT 5)
```

## 2.2.15 いろんな条件を重ねること可能
countとexistsなどでもつなげていましたが、検索条件や並べ替えなどなどぺたぺたくっつけることができます。

```python
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で渡す必要あるとか、キーがついてるのが邪魔というときに。

```python
>>> 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()`を使うと良い。

```python
>>> 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
```


データがすでにあるなら次へ
データないなら、

https://qiita.com/Bashi50/items/95cd4bed8f531509db0f

のように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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/65bd08cd-8783-b5c4-d7a3-480b23fcb857.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()使える
例えば以下のような条件の場合当たり前ですが、取得できるのは一つだけです。これを取り出して、、、なんてのはちょっと冗長。

```python
customer = Customer.objects.filter(id=1)
print(customer[0])
```

`get()`使おう。利点としては

- 1行でスッキリ一つのデータ取り出せる
- データが２つ存在したらエラー出してくれる
- なかったらエラー出してくれる

```python
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対多と多対多](https://qiita.com/Bashi50/items/2cdff211c45e54a4c17c)