[Python][Django]DB検索のあれこれをまとめ

まず初めに今回使用するモデルを紹介

前提として、以下のような Model クラスに対する DB 検索を行うものとします。

from django.db import models
class BookManager(models.Manager):
    pass
class Book(models.Model):
    name = models.CharField(
        verbose_name=("名称"),
        max_length=255
    )
    class = models.IntegerField(
        verbose_name=('分類'),
        choices=(
            (1, '技術'),
            (2, '芸術'),
            (3, 'スポーツ')
            ....
        )
    )
    create_at = models.DateField(
        verbose_name=("作成日時")
    )
    objects = BookManager()

まずは基本的なところから

all

all で全件取得します。QuerySet を返します。

# 全レコードを取得したい場合
books = Book.objects.all()
# SQL だとこんな感じ
select * from books;

get

get で 1 件だけ取得ができます。この例だと Book オブジェクトを返します。

# 1 件だけ取得したい場合
book = Book.objects.get(pk=1)
# PK の名称を指定(ここでは id)しても同じ結果となる
# 基本的には上の pk= の方を使用するのがいいと思う
book = Book.objects.get(id=1)
# SQL だとこんな感じ
select * from books where id = 1;

get で取得する際、存在しない / 結果が複数存在する条件を指定した場合エラーになってしまうので注意が必要です。

# 存在しない条件
Book.objects.get(id=-1)  # DoesNotExist 例外が発生する
# 複数存在する条件
Book.objects.get(name='test')  # MultipleObjectsReturned 例外が発生する

filter

与えられた条件にマッチするレコードを取得します。
複数の条件を指定することができますが、その場合 AND で条件が設定されます。

exclude

与えられた条件にマッチしないレコードを取得します。
複数の条件が指定された場合、AND で結合してそれ全体をNOTで囲います。

# name が django ではない
books = Book.objects.exclude(name='django')
# SQL だとこんな感じ
select * from books where not (name = 'django')

検索条件

filetr などに設定できる条件を解説します。

完全一致

[ カラム名=条件 ]としたり、 [ カラム名__exact=条件 ] とすることで完全一致の条件になります。

= で指定

# 完全一致
books = Book.objects.filter(name='django')
# SQL だとこんな感じ
select * from books where name = 'django'

カンマ区切りで複数の条件を設定することもできます。

# 完全一致(カンマ区切りで複数条件)
books = Book.objects.filter(id=1, name="django")
# SQL だとこんな感じ
select * from books where id = 1 and name = 'django'

exact

自分はあまり使わないですが、以下も完全一致の書き方になります。

# 完全一致
books = Book.objects.filter(id__exact=1)
# SQL だとこんな感じ
select * from books where id = 1

Noneを指定するとNullとして解釈されます。

# 完全一致(Noneを指定)
books = Book.objects.filter(name__exact=None)
# SQL だとこんな感じ
select * from books where name is NULL

ただ、Nullを条件に含めるのであれば、isnullを使用する方が一般的かと思います。

isnull

# Null であるか
books = Book.objects.filter(name__isnull=True)
# SQL だとこんな感じ
select * from books where name is NULL

Falseを指定することで is not NULL となります。

# Null でないか
books = Book.objects.filter(name__isnull=False)
# SQL だとこんな感じ
select * from books where name is not NULL

部分一致

SQLでいうところの LIKE にあたるものです。

contains, icontains

contains は、指定の文字列を含むかをチェックする条件です。
大文字小文字を区別します。

# test を含むか(大文字小文字を区別する)
books = Book.objects.filter(name__contains='test')
# SQL だとこんな感じ
select * from books where name LIKE '%test%'

icontains の場合は大文字小文字を区別しません。

# test を含むか(大文字小文字を区別しない)
books = Book.objects.filter(name__icontains='test')
# SQL だとこんな感じ
select * from books where name ILIKE '%test%'

SQLite は大文字小文字を区別する LIKE ステートメントをサポートしていないそうです。

startswith, istartswith

startswith は、指定の文字列から始まるかをチェックする条件です。
istartswith だと大文字小文字を区別しません。

# test で始まるか(大文字小文字を区別する)
books = Book.objects.filter(name__startswith='test')
# SQL だとこんな感じ
select * from books where name ILIKE 'test%'

endswith, iendswith

endswith は、指定の文字列で終わるかをチェックする条件です。
iendswith だと大文字小文字を区別しません。

# test で始まるか(大文字小文字を区別する)
books = Book.objects.filter(name__endswith='test')
# SQL だとこんな感じ
select * from books where name ILIKE '%test'

正規表現による一致

regex, iregex

正規表現を使用することもできます。
iregex では大文字小文字を区別しません。

# start で始まって、end で終わる文字列を検索
Book.objects.filter(name__regex=r'^start.*end$')

実行されるクエリはデータベースごとに異なります。

# PostgreSQL の場合
SELECT * FROM books WHERE name ~ '^word.*press$';

# MySql の場合
SELECT * FROM books WHERE name REGEXP '^word.*press$';

数値系

gt(greater than), gte(greater than or equal to)

gt は指定の数値より大きいかをチェックする条件です。
gte だと指定の数値以上かをチェックする条件になります。

# id が 5 より大きい
books = Book.objects.filter(id__gt=5)
# SQL だとこんな感じ
select * from books where id > 5

lt(less than), lte(less than or equal to)

lt は指定の数値未満かをチェックする条件です。
lte だと指定の数値以下かをチェックする条件になります。

# id が 5 未満
books = Book.objects.filter(id__lt=5)
# SQL だとこんな感じ
select * from books where id < 5

in

指定されたリストなどに含まれるかをチェックします。
文字列を指定することも可能です。

book = Book.objects.filter(id__in=[1, 3, 4])
book = Book.objects.filter(name__in="test")
SELECT * FROM books WHERE id IN (1, 3, 4);
SELECT * FROM books WHERE name IN ('t', 'e', 's', 't');

range

数値だけでなく、日付や文字列でも使用できます。

# id が 1 ~ 5
books = Book.objects.filter(id__range=(1, 5))
# SQL だとこんな感じ
select * from books where id between 1 and 5

日付系

USE_TZ が True の場合、日付フィールドはフィルタリングの前にカレントタイムゾーンに変換されます。
これにはデータベースの タイムゾーン定義 が必要です。

date, time

datetime のフィールドをそれぞれ date, time としてキャストします。

Book.objects.filter(ceated_at__date=datetime.date(2005, 1, 1))

year

“年” の完全一致で検索します。

# 日付の"年"が完全一致
books = Book.objects.filter(create_at__year=2025)
# SQL だとこんな感じ
select * from books where create_at between '2025-01-01' and '2025-12-31'

他の条件(例えば”以上”を表すgte)と組み合わせて使用することもできます。

# 日付の"年"が完全一致
books = Book.objects.filter(create_at__year__gte=2025)
# SQL だとこんな感じ
select * from books where create_at >= '2025-01-01'

month, day, hour, minute, second

“月” や “日” などを完全一致で検索します。
同じような解説になるのでまとめます。

# 日付の"月"が完全一致
books = Book.objects.filter(create_at__month =12)
# SQL だとこんな感じ
select * from books where EXTRACT('month' FROM create_at) = 12

week_day

“曜日” の一致で検索します。
1 (日曜日) から 7 (土曜日) までの曜日を表す整数値を指定します。

book = Book.objects.filter(created_at__week_day=2)

week

 ISO-8601 に従った週番号 (1~52または53) を返します。

quarter

“四半期” の一致。年の四半期を表す 1 から 4 までの整数値を指定します。
第1四半期(1月1日から3月31日まで)
第2四半期(4月1日から6月30日まで)
第3四半期(7月1日から9月30日まで)
第4四半期(10月1日から12月31日まで)

# 第2四半期(4月1日から6月30日まで)を検索
book = Book.objects.filter(created_at__quarter=2)

iso_year, iso_week_day

ISO 8601の週番号と年、曜日との完全一致で検索します。

QuerySet について

QuerySet は遅延評価になるため、all とか filter とかの時点ではクエリは実行されません。
for で回すとか、list()を呼び出すとかした時にクエリを実行して結果を取得します。

公式サイト:https://docs.djangoproject.com/ja/5.1/ref/models/querysets/#when-querysets-are-evaluated

# イテレートした時にクエリを実行する
for book in Book.objects.all():
    print(book.name)
# list を呼び出すことでクエリを実行する
book_list = list(Book.objects.all())

余談

filter は QuerySet を戻り値として返すため、その QuerySet に対してさらに filter をするなんてこともできてチェーンメソッドのような使い方もできます。
その場合もやはり AND で繋がるので注意です。

私は django で検索機能を実装する時、以下のような形で実装しています。

def search(self, search_conditions):
    # まずは全件取得
    books = Book.objects.filter()
    # 検索条件に name が含まれていれば条件を追加する
    if search_conditions.get('name'):
        books = books.filter(name=search_conditions.get('name'))
    return books

次回は OR 条件で連結する方法であったり、集計関数なんかについてまとめようと思います。

コメント

タイトルとURLをコピーしました