【Django】ORMでSQLを使う - Django初心者

# はじめに
元々現場ではJavaなどの言語を触っていたこともあり、SQLを高頻度で書いていました。

ですがDjangoを触り始めてORMでのクエリ発行になかなか詰まったので、備忘録としてまとめてみました。
　

当記事では以下の構成でまとめていきます。

**1.こんなSQLを実行したい**
**↓**
**2.Djangoでの書き方（解説・補足等）**



# 環境
動作確認は以下の環境で行いました。
* MySQL

# ■SELECT
#### ・データの全件取得
```sql:SQL
SELECT A.*
FROM my_modles A
```
```python:python
from models import MyModel

many_data = MyModel.objects.all()
```
**補足：**
* `all()`を指定することで全件取得が可能


#### ・指定カラムの取得
```sql:SQL
SELECT A.col1, A.col2....
FROM my_modles A
```
```python:python
from models import MyModel

many_data = MyModel.objects.values("col1","col2")
# または
many_data = MyModel.objects.values_list("col1","col2")
# または
many_data = MyModel.objects.only("col1","col2")
```
**補足：**
* `values()` → 値を**辞書型のリスト**で返却する
```python:・values()の戻り値
[
    {'col1': 'value1_1', 'col2': 'value1_2'},
    {'col1': 'value2_1', 'col2': 'value2_2'},
    ...
]
```
　
* `values_list()` → 値を**タプル型のリスト**で返却する
```python:・values_lsit()の戻り値
[
    ('value1_1', 'value1_2'),
    ('value2_1', 'value2_2'),
    ...
]
```

`values_list`は`flat`オプションを持っており**単一カラムの取得時**に便利
```python: values_listのflatオプション
many_data = MyModel.objects.values_list("col1", flat=True).all()

print(many_data)
# タプルではなくリスト形式になり、データへのシンプルなアクセスが可能
# => ['value1', 'value2', 'value3', ...]
```

* `only()` → 値を**モデルのインスタンス**で返却する
```python:・values_lsit()の戻り値
MyModel Instance: <MyModel: MyModel object (1)>, Value1: test_val, Value2: 500.00
MyModel Instance: <MyModel: MyModel object (2)>, Value1: test_val2, Value2: 200.00
```


# ■FROM
**■前提**
`JOIN`に関してはrelationが`model`設定されていることがとても重要になります。

Djangoでは関連先のオブジェクトを取得し、クエリ発行回数を減らすことでパフォーマンスの向上につながります。

https://note.com/shinya_hd/n/n240c3613b60f

### ・INNER JOIN
```sql:SQL
SELECT A.*, B.*
FROM my_modles A
INNER JOIN other_models B
ON A.pk = B.my_models_id
```
```python:python
from models import MyModel
from models import OtherModel

many_data = MyModel.objects.select_related("other_model").all()
```
**補足：**
* [`select_related`](https://docs.djangoproject.com/en/4.0/ref/models/querysets/#select-related)を使用することで`INNER JOIN`が可能になる
　○仕様
 　 　 - 対一の関係性を持つテーブルを取得する
 　 　  　 → 多対一（`ForeignField`）、一対一（`OneToOneField`）
```python:python
# 関連先のオブジェクトデータを取得する
many_data = MyModel.objects.select_related("other_model").all()
other_data = many_data.other_model
```

### ・LEFT JOIN
```sql:SQL
SELECT A.*, B.*
FROM my_modles A
LEFT OUTER JOIN other_models B
ON A.pk = B.my_models_id
```
```python:python
from models import MyModel
from models import OtherModel

many_data = MyModel.objects.prefetch_related("other_model").all()
```
**補足：**
* [`prefetch_related`](https://docs.djangoproject.com/en/4.0/ref/models/querysets/#prefetch-related)を使用することで`LEFT JOIN`が可能になる

　○仕様
 　 　 - 対多の関係性を持つテーブルを取得する
 　 　  　 → 多対多（`ManyToManyField`）
        
※厳密には異なる箇所がありますが割愛させてください。


# ■WHERE
### ・取得件数
```sql:SQL
SELECT *
FROM my_modles A
WHERE A.hoge_col = "hoge"
```
```python:python
from models import MyModel

# 複数件取得
many_data = MyModel.objects.filter(hoge_col="hoge")

# 1件取得
many_data = MyModel.objects.filter(hoge_col="hoge").first()
#または
many_data = MyModel.objects.get(pk=1)
```
**補足：**
* `filter`と`get`の違いと特徴
`get`：
　- 単一のオブジェクトを返却する。
　- 取得件数が0件の場合`DoesNotExist`エラーが発生する

`filter`：
　- 複数のオブジェクトを返却する。
　- 取得件数が0件でもエラーが発生しない。
　- 取得オブジェクトに対し`.query`を使用し、発行されるクエリを確認できる


### ・演算子
#### ⚪︎AND／OR
**■前提**
Djangoではデフォルトの論理演算子は`AND`が使用されてしまう。

論理演算子を容易に実装可能にしてくれるのが、[`Qオブジェクト`](https://docs.djangoproject.com/en/4.0/ref/models/querysets/#q-objects)
である
```sql:SQL
SELECT *
FROM my_modles A
WHERE
    (A.hoge_col1 = "hoge" and not A.hoge_col2 = "huga")
    or A.hoge_col3 = "piyo"
    
```
```python:python
from django.db.models import Q
from models import MyModel

many_data = MyModel.objects.filter(
    ( Q(hoge_col1="hoge") & ~Q(hoge_col2="huga") ) 
    | Q(hoge_col3="piyo")
    
)
```
**補足：**
* `AND` は `&` 記号を用いる
* `OR` は `｜（パイプ）` 記号を用いる
* `NOT`の場合 は `~` 記号を`Q`の前に用いる


 
#### ⚪︎比較
```sql:SQL
SELECT *
FROM my_modles A
WHERE
    A.number1 > hoge
    or A.number2 >= 3
    or A.number3 < 3
    or A.number4 <= 3
```
```python:python
from django.db.models import Q
from models import MyModel

many_data = MyModel.objects.filter(
    Q(number1__gt=3) 
    | Q(number2__gte=3) 
    | Q(number3__lt=3) 
    | Q(number4__lte=3) 
)
```
**補足：**
* `__`（アンダースコア２つ）が必要



#### ⚪︎LIKE句
```sql:SQL
SELECT *
FROM my_modles A
WHERE
    A.hoge_col1 LIKE '%hoge'
    OR A.hoge_col2 LIKE 'hoge%'
    OR A.hoge_col3 LIKE '%hoge%'
```
```python:python
from django.db.models import Q
from models import MyModel

many_data = MyModel.objects.filter(
     Q(hoge_col1__startwith="hoge")
     | Q(hoge_col2__endwith="hoge")
     | Q(hoge_col3__contains="hoge")
)
```
**補足：**
* 前方一致 → `startwith` 
* 後方一致 → `endwith`
* 部分一致 → `contains`
* それぞれの頭に`i`を付与することで大文字、小文字の区別を行わなくなる（例：`istartwith`）



#### ⚪︎IN句
```sql:SQL
SELECT *
FROM my_modles A
WHERE
    A.hoge_col in (1,3)
```
```python:python
from models import MyModel

list = [1,3]
many_data = MyModel.objects.filter(hoge_col__in=list)
```
**補足：**
* `__`（アンダースコア２つ）が必要
* 検索したい値を`list`または`tuple`で渡す


#### ⚪︎BETWEEN句
```sql:SQL
SELECT *
FROM my_modles A
WHERE
    A.hoge_col BETWEEN 1 and 10
```
```python:python
from models import MyModel

list = [1,3]
many_data = MyModel.objects.filter(A.hoge_col__range=(1, 10))
```
**補足：**
* `__`（アンダースコア２つ）が必要



# ■GROUP BY
```sql:SQL
SELECT A.id, A.name, SUM(A.hoge_num) as sum_data
FROM my_modles A
GROUP BY A.id, A.name
```
```python:python
from django.db.models import Sum
from models import MyModel

many_data = MyModel.objects.annotate(sum_data=Sum("hoge_col")).only("id", "name", "sum_data")
```
**補足：**
* `annotate`を用いて集計を行うカラムを用意
* 内部的に`GROUP_BY`してくれるが、必要に応じて`only()`メソッドを利用して任意の`GROUP BY`が可能になる

# ■HAVING
```sql:SQL
SELECT A.id, A.name, SUM(A.hoge_col) as sum_data
FROM my_modles A
WHERE name LIKE '%hoge%'
GROUP BY A.id, A.name
HAVING sum_data > 5
```
```python:python
from django.db.models import Sum
from models import MyModel

many_data = MyModel.objects
    .filter(name__contains="hoge")
    .annotate(sum_data=Sum("hoge_col"))
    .filter(sum_data__gt=5)
```
**補足：**
* `annotate`を用いたのちに`filter`で`HAVING`の指定が可能
* `WHERE`、`HAVING`を一つの`filter`で指定できない


# ■ORDER BY
```sql:SQL
SELECT A.*
FROM my_modles A
ORDER BY A.hoge_col asc
--ORDER BY A.hoge_col desc
```
```python:python
from models import MyModel

asc_data = MyModel.objects.all().order_by("hoge_col")
desc_data = MyModel.objects.all().order_by("-hoge_col")
```
**補足：**
* `desc（降順）`の場合から無名の前に`-`をつける



# ■その他関数
### ・COUNT
```sql:SQL
SELECT A.col1, A.col2, Count(A.col3) as count_col
FROM my_modles A
```
```python:python
from django.db.models import Count
from models import MyModel

many_data = MyModel.objects.annotate(count_col=Count("col3"))
```

### ・SUM
```sql:SQL
SELECT A.col1, A.col2, Sum(A.col3) as sum_col
FROM my_modles A
```
```python:python
from django.db.models import Sum
from models import MyModel

many_data = MyModel.objects.annotate(sum_col=Sum("col3"))
```

### CASE／WHEN
```sql:SQL
SELECT
 A.col1
 , A.col2
 , CASE col3 
    WHEN 1 THEN "one" 
    WHEN 2 THEN "two" 
    WHEN 3 THEN "three" 
    ELSE "other"
 END as case_col
FROM my_modles A
```
```python:python
from django.db.models import Case, When
from models import MyModel

many_data = MyModel.objects.annotate(
     case_col=Case(
         When(col3=1, then=Value("one"))
        ,When(col3=2, then=Value("two"))
        ,When(col3=3, then=Value("three"))
        ,default=Value("other")
        ,output_field=models.CharField()
     )
```
**補足：**
* `Case`内に`When`を引数として渡し使用する
* `default`は`ELSE`の役割を担う
* `output_field`は戻り値の型を明示的に示す

# ■INSERT
更新中・・・


# ■UPDATE
更新中・・・