valuesとvalues_list

valuesとvalues_listでクエリを最適化する

一般的に、Djangoのクエリで取得したデータは、クエリセットやオブジェクトといった特殊なデータ型です。これらはフィールドの値以外にも多くの情報を他に持っていてデータ量が必然的に多くなります。

Djangoのvaluesやvalues_listを使えば、該当するフィールドの値のみを取り出すことができるので処理を最適化することができます。
特に外部キーでのリレーションが多かったり、データ量が増えてくるとそれに応じてデータを取得するために要する時間が増えてきます。

こういった時にvaluesやvalues_listを使えば、時にクエリに要する時間をかなり縮めることができます。
今回も下記のmodels.pyを使用します。

models.py


class Article(models.Model):

    title = models.CharField(max_length=100)
    body = models.TextField()
    is_published = models.BooleanField(default=False)
    created = models.DateTimeField(auto_now_add=True)


class Comment(models.Model):

    article = models.ForeignKey(Article, on_delete=models.CASCADE)
    body = models.TextField()
    created = models.DateTimeField(auto_now_add=True)

values

valuesで取り出されるデータは、Pythonの辞書型のようなものになっていて、ValuesQuerySetと呼ばれます。
扱い方は辞書型と同じでキーを指定して値を取り出すことができます。
キーは通常フィールド名と同じになります。

データ個数が複数のクエリセットの場合はfilter()やall()の後にvalues()を加えます。
データ個数が単体のオブジェクトの場合は、get()のvalues()を付けます。

from .models import Article

# 例1
articles = Article.objects.filter(is_published=True).values()
# 例2
articles = Article.objects.filter(is_published=True).values('title', 'body')

# 単体の例
test_article = Article.objects.values('title', 'body').get(title='test')
test_title = test_article['title']

for article in articles:
    title = article['title']
    body = article['body']

```’

例1では、values()の引数に何も指定していません。
この場合全てのフィールドを参照します。
例2では、values()の引数にフィールドを指定しました。
この場合、指定したフィールドのみを指定して引っ張ってくることができます。

データの取り出し方は、辞書型と全く同じ扱い方です。

`objects.get()`を使用した単体のクエリの場合は、get()の前にvalues()を付けています。

#### values_list
values_listで取り出されるデータは、Pythonのタプル型になっています。

また引数に指定するフィールドが1つの場合、第二引数に`flat=True`を加え、list()で囲むことでリスト型としてデータを取得することができます。

values_listとしてデータを取得した時はフィールド名ではなく、インデックスでデータを取り出します。
引数に指定した順番でデータを取り出すことができます。

例
```.py
from .models import Article

# 複数の場合
articles = Article.objects.filter(is_published=True).values_list('title', 'body')
# Article.titleのリストとして取り出す。
articles_list = list(Article.objects.filter(is_published=True).values_list('title', flat=True))

# 単体の場合フィールドのみを取得することができる
test_title = Article.objects.values_list('title', flat=True).get(title='test')

for article in articles:
    title = article[0]
    body = article[1]


values_listを使用するときも引数にフィールド名を指定するだけと意外に簡単です。
またフィールドが一つの時にflat=Trueを指定して、リストとして取り出しているのが2つめの例です。

objects.get()で取り出す場合も、flat=Trueを使うことでフィールドの値のみを取り出すことができます。(3つめの例)

実はvalues_listで取り出すほうがvaluesよりも少し早くなります。キーなどが存在しないからでしょう。
ただしプロダクト開発を進めていく時にインデックス指定でデータを取り出していくとコードの可読性が悪くなります。

実際、for文でtitleとbodyを取得していますが、変数名を見ないとどんなデータかわかりません。

データが複数の場合はvalues、配列として取り出したい時や単体のデータの値のみ取得したい場合は、values_listを使用すると良いかもしれません。

外部キーを参照する

valuesやvalues_listは、外部キーで紐づいているデータも取得することができます。
この場合、__のようにアンダーバーを二つ繋げることで次のデータテーブルを参照することができます。

from .models import Comment

comments = Comment.objects.all().values('article__title', 'article__body')

うまく使い分ける

クエリでデータを取得する場合、できるだけvaluesやvalues_listを使用しておいたほうが良いでしょう。
APIなどで使用する際にもクエリセットのデータ型はJSONに変換することができないので、values()を使わなければいけません。

ただしクエリセットを逆に使用しなければいけないケースも出てきます。

1つは、Modelメソッドを使用したいときです。
Modelメソッドはクエリセットと紐づいています。そのためvalues()でデータを引っ張ってくるとmodels.pyのclassに作成しているModelメソッドの関数は使用できなくなってしまいます。

他にもFileFieldやImageFieldのURLを参照する時などもvalues()では引っ張ってこれません。
DjangoではクエリからイメージなどのURLを取得する時、article.image.urlなどのように最後にurlを指定します。values()やvalues_list()でイメージやファイルを指定するとその名前を取得し、URLを取得してくることはしません。
このためアイコンやイメージ、ファイルなどのデータはクエリセットで取得するケースが多くなります。

実際に開発を進める時は、
できるだけvalues()やvalues_list()を使用し、それが面倒であったり、可能でなければ通常のクエリセットやオブジェクトでデータを取得するといった風にすると良いでしょう。