0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python(Django) x Docker x AWSAdvent Calendar 2023

Day 25

【Django】Qオブジェクトを使って検索するサンプルコードを6種類解説

Posted at

概要

DjangoのソースコードでQオブジェクトを使いました。
本記事では実際にサンプルコードを使って実行してみたので、紹介します。

前提

Qオブジェクトとは、Djangoのクエリを構築する際、複数の条件を組み合わせてデータベースからデータをフィルタリングするために使われます。大体の場合、filter()メソッドとセットで使われるはずです(getは単一のレコードを取得することが目的のため)。

今回は国名を検索するコードとして、Qオブジェクトを使います。
モデル定義は以下の通り。

models.py
class Country(models.Model):
    name = models.CharField(max_length=100, null=True, blank=True)
    capital = models.CharField(max_length=100, null=True, blank=True)
    population = models.IntegerField(null=True, blank=True)
    continent = models.CharField(max_length=50, null=True, blank=True)
    language = models.CharField(max_length=50, null=True, blank=True)
    currency = models.CharField(max_length=50, null=True, blank=True)

    class Meta:
        db_table = 'country'

テーブルには、以下のようにレコードが入っている前提です。

image.png

サンプルコード解説

以下のサンプルコードを紹介します。

  • OR検索
  • AND検索
  • 複数条件の組み合わせ検索
  • 部分文字列の一致検索
  • 否定条件の検索

OR検索を行う

一般的に、Qオブジェクトを使うときは以下のようにOR条件を扱いたい場合が多いと思います。

from response.models import Sample, Country

from django.db.models import Q

# 人口が1億以上または南米に属する国を取得するクエリの作成
q_obj = Q(population__gte=100000000) | Q(continent='South America')

# Qオブジェクトを使ってフィルタリングする
results = Country.objects.filter(q_obj)

# 結果の表示
print("人口が1億以上または南米に属する国は...")
for country in results:
    print(f"[{country.id}] 国名: {country.name}, 人口: {country.population}, 地域: {country.continent}")

↓出力結果

人口が1億以上または南米に属する国は...
[1] 国名: Japan, 人口: 123294513, 地域: Asia
[2] 国名: USA, 人口: 340870790, 地域: North America
[6] 国名: China, 人口: 1425433798, 地域: Asia
[7] 国名: Brazil, 人口: 216422446, 地域: South America

gteは「以上」を意味しています。ちなみにgtだと「〜より大きい」になります。
同じように、lteが「以下」、ltが「未満」です。

また、上記のq_objtypeをプリントすると、以下になります。

<class 'django.db.models.query_utils.Q'>

DjangoのORMでクエリ条件を構築するために使われるQオブジェクトの型であることがわかります。

OR検索を行う(その2)

# 英語を公用語とする国またはヨーロッパに属する国を取得するクエリの作成
q_obj = Q(language='English') | Q(continent='Europe')

# Qオブジェクトを使ってフィルタリングする
results = Country.objects.filter(q_obj)

# 結果の表示
print("英語を公用語とする国またはヨーロッパに属する国は...")
for country in results:
    print(f"[{country.id}] 国名: {country.name}, 人口: {country.population}, 地域: {country.continent}")

↓出力結果

英語を公用語とする国またはヨーロッパに属する国は...
[2] 国名: USA, 人口: 340870790, 地域: North America
[3] 国名: France, 人口: 64816918, 地域: Europe
[4] 国名: United Kingdom, 人口: 68000000, 地域: Europe
[5] 国名: Germany, 人口: 83274299, 地域: Europe

ちなみに以下のようにstartswithを使用して「Euro」から始まる文字列の一致検索を行うこともできます。

q_obj = Q(language='English') | Q(continent__startswith='Euro')

AND検索を行う

# 人口が7000万人以上かつヨーロッパに属する国を取得するクエリの作成
q_obj = Q(population__gte=70000000, continent='Europe')

# Qオブジェクトを使ってフィルタリングする
results = Country.objects.filter(q_obj)

# 結果の表示
print("人口が7000万人以上かつヨーロッパに属する国は...")
for country in results:
    print(f"[{country.id}] 国名: {country.name}, 人口: {country.population}, 地域: {country.continent}")

↓出力結果

人口が7000万人以上かつヨーロッパに属する国は...
[5] 国名: Germany, 人口: 83274299, 地域: Europe

AND条件も可能ですが、filter()メソッドだけでも複数の条件をカンマでつなげれば可能ですので、Qオブジェクトを使う旨味はあまりなさそう。ですので、AND条件を使う必要がある場合は、以下の「複数の条件を組み合わせたい」時になるかと思います。

複数の条件を組み合わせた検索を行う

以下のように&を使うことで、異なる検索条件のQオブジェクトをAND検索で指定することが可能になります。

# 「人口が3億以上」または「人口が5000万から1億未満かつヨーロッパに属する」国を取得するクエリの作成
q_obj = (Q(population__gte=300000000) |
         (Q(population__range=(50000000, 99999999)) & Q(continent='Europe')))

# Qオブジェクトを使ってフィルタリングする
results = Country.objects.filter(q_obj)

# 結果の表示
print("「人口が3億以上」または「人口が5000万から1億未満かつヨーロッパに属する」国は...")
for country in results:
    print(f"[{country.id}] 国名: {country.name}, 人口: {country.population}, 地域: {country.continent}")

↓出力結果

「人口が3億以上」または「人口が5000万から1億未満かつヨーロッパに属する」国は...
[2] 国名: USA, 人口: 340870790, 地域: North America
[3] 国名: France, 人口: 64816918, 地域: Europe
[4] 国名: United Kingdom, 人口: 68000000, 地域: Europe
[5] 国名: Germany, 人口: 83274299, 地域: Europe
[6] 国名: China, 人口: 1425433798, 地域: Asia

部分文字列の一致検索を行う

icontainsを使って部分文字列の一致を行うことができます。

# continentフィールドで"America"を含む国
q_obj = Q(continent__icontains='America')

# Qオブジェクトを使ってフィルタリングする
results = Country.objects.filter(q_obj)

# 結果の表示
print("アメリカ大陸にある国は...")
for country in results:
    print(f"[{country.id}] 国名: {country.name}, 人口: {country.population}, 地域: {country.continent}")

↓出力結果

アメリカ大陸にある国は...
[2] 国名: USA, 人口: 340870790, 地域: North America
[7] 国名: Brazil, 人口: 216422446, 地域: South America

ちなみに、icontainsは大文字と小文字を区別せずに部分文字列を検索するためのもので、containsは完全に指定した文字列を含んでいるかをチェックする際に使用されます。

上記の例だと例えばブラジルのレコードで大陸を「South america」にした状態でcontainsにして検索を行うと、「america」と「America」で区別されるのでブラジルがヒットしません。

否定条件で検索を行う

最後に。
否定条件で検索を行う場合は、Qオブジェクトに~を指定します。
特定の条件を満たさないレコードをフィルタリングしたいときに使えますね。

# 貨幣がEuroの国を否定(NOT)
not_q_obj = ~Q(currency='Euro')

# 否定条件を使ってフィルタリングする
results = Country.objects.filter(not_q_obj)

# 結果の表示
print("貨幣がEuroではない国は...")
for country in results:
    print(f"[{country.id}] 国名: {country.name}, 人口: {country.population}, 地域: {country.continent}")

↓出力結果

貨幣がEuroではない国は...
[1] 国名: Japan, 人口: 123294513, 地域: Asia
[2] 国名: USA, 人口: 340870790, 地域: North America
[4] 国名: United Kingdom, 人口: 68000000, 地域: Europe
[6] 国名: China, 人口: 1425433798, 地域: Asia
[7] 国名: Brazil, 人口: 216422446, 地域: South America
0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?