【Django】011. データベースの機能を少し深堀る

前回はManagerのfilterメソッドによるデータベースの検索を行いました。
[前回記事：【Django】010. 検索をマスターする](https://qiita.com/ys_dirard/items/7cf2e6b6544e299daa88)


今回はデータベースのレコードの並べ替え、集計等を行ってみます。

※一般的な機能ではあるので深堀りと言えるかは怪しいです…。

今回も以下の本を参考にしています。

https://www.shuwasystem.co.jp/book/9784798062419.html


今回も引き続き以下のFriendモデルを使用します。

```python
class Friend(models.Model):
    name = models.CharField(max_length=100)
    mail = models.EmailField(max_length=200)
    gender = models.BooleanField()
    age = models.IntegerField(default=0)
    birthday = models.DateField()
    
    def __str__(self):
        return '<Friend:id=' + str(self.id) + ', ' + \
            self.name + '(' + str(self.age) + ')>'

```

## レコードの並び替え

allやfilter等で多数のレコードを検索したとき、基本的には自動生成されるID番号順 (作成順) に表示されます。

他の基準で並び変えるにはManagerクラスのorder_byメソッドを以下のように使用し実行できます。

```python
<Model>.objects.<all, filter, ...>.order_by(項目名)
```

テンプレートは以下とします。
```html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

</head>
<body class="container">
    <h1 class="display-4 text-primary">{{ title }}</h1>
    <p>{{ message | safe }}</p>
    <table class="table">
        <tr>
            <th>id</th>
            <th>name</th>
            <th>age</th>
            <th>mail</th>
            <th>birthday</th>
        </tr>
        {% for item in data %}
        <tr>
            <td>{{ item.id }}</td>
            <td>{{ item.name }}</td>
            <td>{{ item.age }}</td>
            <td>{{ item.mail }}</td>
            <td>{{ item.birthday }}</td>
        </tr>
        {% endfor %}
    </table>
</body>
</html>

```

### 年齢順に並び替える
ビュー関数を以下とします。

```python
def order(request):
    data = Friend.objects.all().order_by('age')
    params = {
        'title': 'Hello',
        'message': '',
        'data': data,
    }
    return render(request, 'hello/show.html', params)
    
```

allで検索した後にorder_by('age')で並び替えています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/751f6fd1-727f-4d9e-ea83-29ad80aa037e.png)

アクセスするとageについて昇順に並んでいることが確認できました。

逆順 (降順) にする場合はreverseという関数を追加します。
```python
<Model>.objects.<all, filter, ...>.order_by(項目名).reverse()
```

実際に以下のようにすると…

```python
def order(request):
    data = Friend.objects.all().order_by('age').reverse()
    params = {
        'title': 'Hello',
        'message': '',
        'data': data,
    }
    return render(request, 'hello/show.html', params)
    
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/9092e411-9a03-c6d6-c92d-7265211f2f06.png)

逆順で表示されることが確認できました。


## 指定した範囲のレコードを取り出す
レコード数が多くなってくると全部取得して表示！は非現実的です。

allやfilterを使用して取得されるのはQuerySetというクラスのインスタンスでした。

QuerySetはListと同様に[]で取り出すインデックスを指定できます。

ビュー関数を以下のようにしてみます。
```python
def order(request):
    data = Friend.objects.all()[1:3]
    params = {
        'title': 'Hello',
        'message': '',
        'data': data,
    }
    return render(request, 'hello/show.html', params)
    
```

レコードは現在IDが1, 2, 3, 4の4レコードが存在しており、indexが1→ID2、3→ID4なのでID2~3までが出力されます。

出力結果もそのようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/8ead0211-4e76-af54-f16c-337dc027d44c.png)


## レコードの集計 (aggregate)
多数の数値データを扱うときなど、値を取り出すだけではなく集計処理をすることもあります。

合計、平均等の一般的な集計であればaggregateメソッドで行うことができます。

```python
変数 = <Model>.objects.aggregate(関数)
```

aggregateの引数にはdjango.db.modelsに用意されている集計用の関数を使用します。

関数例：
|関数名|詳細|
|-|-|
|Count(項目名)|指定した項目のレコード数を返す|
|Sum(項目名)|指定した項目の合計を返す|
|Avg(項目名)|指定した項目の平均を返す|
|Min(項目名)|指定した項目の最小値を返す|
|Max(項目名)|指定した項目の最大値を返す|

ビュー関数を以下のようにします。

```python
def aggregate(request):
    data = Friend.objects.all()
    
    re1 = Friend.objects.aggregate(Count('age'))
    re2 = Friend.objects.aggregate(Sum('age'))
    re3 = Friend.objects.aggregate(Avg('age'))
    re4 = Friend.objects.aggregate(Min('age'))
    re5 = Friend.objects.aggregate(Max('age'))
    
    msg = 'count: ' + str(re1['age__count']) \
        + '<br>Sum: ' + str(re2['age__sum']) \
        + '<br>Average: ' + str(re3['age__avg']) \
        + '<br>Min: ' + str(re4['age__min']) \
        + '<br>Max: ' + str(re5['age__max'])
    params = {
        'title': 'Hello',
        'message': msg,
        'data': data,
    }
    return render(request, 'hello/show.html', params)
    
```

結果の取得はre1['age__count']のように行います。

キーとして
```
'項目名__関数名'
```
を指定することで取得可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/0a81f8c7-3fad-b32a-66e8-1c0b4a9a83d8.png)

アクセスすると集計できていることが確認できました。


## SQLを直接実行する

Djangoのfilter等の機能を使うと大体のことはできるかと思いますが、複雑な処理を行う際にSQLを直接実行したくなることがあるかもしれません。
(本当にそのような状況があるかはわかりません)

SQLを直接実行するためにはManagerクラスのrawメソッドを使用します。
```python
変数 = <Model>.objects.raw(クエリ文)
```

ビュー関数を以下のようにします。
```python
def sql(request):
    sql_text = 'select * from hello_friend where age <= 30'
    data = Friend.objects.raw(sql_text)
    
    params = {
        'title': 'Hello',
        'message': '',
        'data': data,
    }
    return render(request, 'hello/show.html', params)
```

Djangoでは実際に作成されるデータベースのテーブル名はhello_friendのように
```
アプリ名_モデル名
```
になります。

今回はhello_friendテーブルからageが30以下となるレコードを抽出しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/f0a94d06-2ba7-6581-bee0-1635c4856241.png)

SQLを直接実行できるようになったのでアプリの自由度はものすごく上がりました。

ただ、エラーのもとになったりちゃんと使わないとセキュリティー的に問題が出てきたりしそうなのでなるべく使わない方が良いと思われます。

## まとめ
今回はDjangoのデータベース周りを一歩踏み込んでみました。

並べ替えや集計はよく使うと思うので覚えておきたいです。

SQLも直接使えるんだな～ということで最悪これで何とかします。(使いたくはない)