クエリを生成する

revision-up-to:17812 (1.4)

データモデル を作成したら、次はデータベースからデー タを取り出す必要があります。このドキュメントでは、モデルから利用できるデー タベース抽象化 API と、オブジェクトを生成、取得、更新する方法について説明し ます。モデルの照合オプションの詳細は データモデルリファレンス を参照してください。

このリファレンスでは、以下のような Poll アプリケーションを参考に話を進めま す:

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __unicode__(self):
        return self.name

class Author(models.Model):
    name = models.CharField(max_length=50)
    email = models.EmailField()

    def __unicode__(self):
        return self.name

class Entry(models.Model):
    blog = models.ForeignKey(Blog)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateTimeField()
    mod_date = models.DateTimeField()
    authors = models.ManyToManyField(Author)
    n_comments = models.IntegerField()
    n_pingbacks = models.IntegerField()
    rating = models.IntegerField()

    def __unicode__(self):
        return self.headline

オブジェクトの生成

Django では、データベーステーブル上のデータを Python オブジェクトで表現する ために、モデルクラスがデータベーステーブルを表現し、クラスのインスタンスが テーブル上のレコードを表現するという直感的なシステムを使っています。

オブジェクトを生成するには、キーワード引数を使ってモデルクラスのインスタン スを生成し、 save() メソッドを呼び出して データベースに保存します。

モデルクラスは Python パス上のどこからでも import でき、期待通りに動作しま す (わざわざこのような説明をするのは、以前のバージョンの Django ではモデル の import 方法がかなり風変わりだったからです)。

モデルが mysite/blog/models.py というファイルで定義されているとすると、 オブジェクトの作成は以下の例のようになります:

>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()

この操作によって、背後では INSERT SQL 文が実行されます。 Django はユー ザが明示的に save() を呼び出すまで データベースを操作しません。

save() メソッドには戻り値がありません。

See also

save() には高度な使い方のための オプションがありますが、ここでは解説しません。詳しくは save() のドキュメントを参照してください。

ワンステップでオブジェクトを生成して保存するには create() メソッドを使って ください。

オブジェクトへの変更を保存する

すでにデータベース上にあるオブジェクトへの変更を保存するには save() を使います。

Blog インスタンス b5 がすでにデータベース上にあるとすると、以下の例 は b5 の名前を変更して、データベース上のレコードを更新します:

>> b5.name = 'New name'
>> b5.save()

この例では、背後で UPDATE SQL 文が実行されています。 Django は明示的に save() を呼び出すまでデータベースを操作しません。

ForeignKeyManyToManyField の保存

ForeignKey フィールドの更新は、 通常のフィールドへの変更と同じです。すなわち、 適切な型のオブジェクトを代入して保存すると、フィールドの値を更新でき ます。この例では、Entry のインスタンス entry``の ``blog アトリビュートを更新しています。:

>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
>>> entry.blog = cheese_blog
>>> entry.save()

ManyToManyField の更新は少し違います。 リレーションにレコードを一つ追加したい場合は add() メソッドを使いま す。この例では、 entry オブジレェクトに、 Author インスタンスの joe を加えています:

>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)

複数のレコードを ManyToManyField に一度で加えたい 場合、 add() に、 複数の引数をくるんで呼び出せばいいのです。:

>>> john = Author.objects.create(name="John")
>>> paul = Author.objects.create(name="Paul")
>>> george = Author.objects.create(name="George")
>>> ringo = Author.objects.create(name="Ringo")
>>> entry.authors.add(john, paul, george, ringo)

間違った型のオブジェクトを外部キーに代入したり add() したりすると Django はエラーを出します。

オブジェクトの取得

オブジェクトをデータベースから取得するには、モデルクラスのマネジャ Manager を介してクエリセット QuerySet を構築します。

QuerySet はデータベース上にある オブジェクトの集まりを表現しています。 QuerySet`には、集合を指定パラメタに従って 絞り込むための条件である*フィルタ (filter)* を複数個持たせられます。 SQL 用語でいえば、クエリセットは ``SELECT` 文であり、 フィルタは WHERELIMIT のような限定節にあたります。

QuerySet はモデル の:class:~django.db.models.Manager から取得します。モデルには最低一つの Manager があり、デフォルトでは objects という 名前がついています。マネジャにはモデルクラスから直接アクセスしてください:

>>> Blog.objects
<django.db.models.manager.Manager object at ...>
>>> b = Blog(name='Foo', tagline='Bar')
>>> b.objects
Traceback:
    ...
AttributeError: "Manager isn't accessible via Blog instances."

Note

(「テーブルレベル」の操作と「レコードレベル」の操作を分離させるため、マ ネジャはモデルのインスタンスではなくモデルクラスだけからアクセスできる ようになっています。)

モデル内での QuerySets の主なソースは Manager です。 マネジャは、データベースオブジェクト上の全てのオブジェクトを表す「ルートの」 クエリセットであるかのように振舞います。例えば、初期クエリセットである Blog.objects には、データベース上の全ての Blog オブジェクトが入っています。

全てのオブジェクトの取得

テーブルからオブジェクトを取得する最も単純な方法は全てのオブジェクトを取得 するというものです。全オブジェクトを取得するには、 Managerall() メソッドを使って下さい:

>>> all_entries = Entry.objects.all()

all() メソッドはデータベース上の全ての オブジェクトを表現する QuerySet() を返します。

(Entry.objects がクエリセットを返すというのなら、なぜ単に Entry.objects と書かないのでしょうか?それは、ルートのクエリセットであ る Entry.objects が特別扱いされていて、値評価できないようになっているか らです。 all() メソッドは、値評価 できる クエリセットを返します。

フィルタを使ってオブジェクトを取り出す

Manager によって提供される、 いわゆるルート QuerySet を使えば、 データベーステーブル上の全てのオブジェクトを表せます。とはいえ、 通常は全オブジェクトの集合からサブセットだけを取り出したいことでしょう。

サブセットを作成するには、フィルタ条件を追加して、 初期 QuerySet を リファインする必要があります。 QuerySet の洗練には、主に二つの方法があります:

filter(**kwargs)
指定した照合パラメタに一致するオブジェクトの集合を表現する、新たな QuerySet を返します。
exclude(**kwargs)
指定した照合パラメタに一致 しない オブジェクトの集合を表現する、 新たな QuerySet を返します。

照合パラメタ (上の関数定義における **kwargs) は、後述の フィールドの照合 で解説するフォーマットにせねばなりません。

例えば、 2006 年のブログエントリを表す QuerySet を取得するには、以下のように filter() を使います:

Entry.objects.filter(pub_date__year=2006)

(Entry.objects.all().filter(...) のように、 all() を使わなくてもよいことに注意して下さい。 all() を使っても問題なく動作しますが、 all() が必要となるのはルートクエリセットから 全てのオブジェクトを取り出したい場合だけです。)

フィルタの連鎖

QuerySet をリファインした結果は、 それ自体 QuerySet になります。 従って、リファイン操作は連鎖させられます。例えば:

>>> Entry.objects.filter(
...     headline__startswith='What'
... ).exclude(
...     pub_date__gte=datetime.now()
... ).filter(
...     pub_date__gte=datetime(2005, 1, 1)
... )

この例では、データベースの全てのエントリを表す初期クエリセットに対し、 filter() をかけた後に exclude() を実行し、さらにもう一つ filter() をかけています。最終的に得られるのは、 “What” で始まるヘッドラ インのうち、 January 1, 2005 から今日までの間に公開されたエントリです。

フィルタしたクエリセットは一意なオブジェクトである

QuerySet のリファインを行うと、 その都度新たなクエリセットを得ます。新たな QuerySet は 以前のクエリセットになんら縛られていません。リファイン操作のたびに、 別個の独立した QuerySet が作成され、個別に保存したり、 再利用したりできます。

例えば:

>> q1 = Entry.objects.filter(headline__startswith="What")
>> q2 = q1.exclude(pub_date__gte=datetime.now())
>> q3 = q1.filter(pub_date__gte=datetime.now())

これら 3 つのクエリセットは別個のものです。最初はヘッドラインが “What” で始 まる全てのエントリの入ったベースのクエリセットです。二つ目のクエリセットは、 最初のクエリセットのサブセットであり、 pub_date の値が現在時刻よりも大 きいものを排除します。三つ目のクエリセットも最初のクエリセットのサブセット で、 pub_date の値が現在時刻よりも大きいものだけを選択するようになって います。こうしたリファイン操作は、初期クエリセット (q1) に影響を及ぼし ません。

クエリセットは遅延評価される

クエリセットの評価は遅延型 (lazy) です。すなわち、 QuerySet の作成自体は データベース操作を引き起こしません。 QuerySet評価される までデータ ベースへのクエリを実行しないので、延々フィルタを重ねられます:

>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.now())
>>> q = q.exclude(body_text__icontains="food")
>>> print q

この例では、データベースに 3 度アクセスするように見えますが、実際には一度だ け、最後の行のとき (print q) しかアクセスしません。一般に、 QuerySet の結果は、その内容を「調べる」まで、データベースから取り出され ません。 QuerySet は、クエリの内容を調べたときにはじめて 値評価 され るのです。値評価がいつ行われているかは、 クエリセットはいつ評価されるのか で詳しく説明しています。

ゲットを使って一つのオブジェクトを取り出す

filter() は常に、 QuerySet を渡し、もし一つだけ オブジェクトがマッチするクエリ - のようなケースならば、 これは単一の要素を含んだ QuerySet です。

もし、クエリにマッチするのが一つだけだと分かるなら、 Managerget() メソッドが使えます。 これはオブジェクトを直接的に返します。:

>>> one_entry = Entry.objects.get(pk=1)

filter() と同じように、 クエリは get() と一緒に使えます、 もう一度 フィールドの照合 を見てください。

get() を使うのと、 filter()[0] でスライス するのとでは違いがあります。もし、クエリにマッチするものがなかった時、 get()DoesNotExist 例外を 送出します。この例外はモデルクラスの属性で、クエリは上にあるコードのように 動きます、もし Entry オブジェクトに 1 というプライマリーキーがないなら、 Django は Entry.DoesNotExist 例外を送出します。

同様に、 Django は get() クエリで 一つ以上のアイテムとマッチした場合にも例外を送出します。このようなケースでは、 MultipleObjectsReturned がモデルクラス自身の属性に反しています。

その他のクエリセットメソッド

ほとんどの場合、データベース上のオブジェクトを照合するには、 all() か、 get() filter() exclude() メソッドで事足ります。とはいえ、クエリセットがサポートしているメソッドは もっとたくさんあります。クエリセットメソッドの詳細な説明は、 クエリセット API リファレンス を参照してください。

クエリセットに制約を課す

クエリセットの返す結果を特定の個数に制限したい場合には、 Python の配列スラ イス表記を使います。これは SQL の LIMIT 節や OFFSET 節と等価です。

以下の例は、最初の 5 オブジェクトだけを返します (LIMIT 5 に相当します):

>>> Entry.objects.all()[:5]

以下の例は、 6 番目から 10 番目までのオブジェクトを返します (OFFSET 5 LIMIT 5 に相当します):

>>> Entry.objects.all()[5:10]

負の数でのインデクシングはサポートしていません。 (すなわち、 Entry.objects.all()[-1] このようなものです)

一般に、 QuerySet をスライスしても QuerySet を新たに生成して返すだけで、 クエリの評価は行いません。例外はスライス表記に「ステップ (step)」パラメタを 使ったときです。以下の例では、クエリを実際に実行し、最初の 10 オブジェクト 中から 1 つおきにオブジェクトを取り出したリストを返します:

>>> Entry.objects.all()[:10:2]

リストではなく 単一の オブジェクトを取得したい場合 (SELECT foo FROM bar LIMIT 1 のような場合) には、スライスではなく単純な インデクス指定を行います。以下の例はデータベースのエントリをヘッドラインに ついてアルファベット順に整列した後、最初の Entry を取得して返します:

>>> Entry.objects.order_by('headline')[0]

これはだいたい以下と同じになります:

>>> Entry.objects.order_by('headline')[0:1].get()

ただし、指定条件にマッチするオブジェクトがない場合、前者は IndexError, 後者は DoesNotExist を送出します。詳しくは get() のドキュメントを参照してください。

フィールドの照合

フィールドの照合操作によって、 SQL の WHERE 節の中身が決まります。フィー ルドの照合を行うには、 filter(), exclude() および get() といった QuerySet のメソッドのキーワード引数を指定します。

基本的に、照合のキーワード引数名は field__lookuptype=value のような形 式をとります (アンダースコアは二重です)。例えば:

>>> Entry.objects.filter(pub_date__lte='2006-01-01')

は、(大雑把にいって) 以下のような SQL 文に変換されます:

SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

これがどうして可能なのか

Python には、任意の名前と値をもった引数を受け取れる関数を定義する機能が あり、引数名とその値は実行時に評価されます。くわしい情報は公式の Python チュートリアルの キーワード引数 を参照してください。

Django 1.4 で変更されました: 照合の中でのフィールドの指定は、モデルフィールドの名前でなければいけませ ん。 ForeignKey の場合、例外ががありますが、 フィールドネームを _id というサフィックス(suffix)で指定できます。 このようなケースでは、バリューパラメータはフォーリンモデルの プライマリーキーの生の値を含みます。例えば::
>>> Entry.objects.filter(blog_id__exact=4)

照合時に無効なキーワード引数が渡されると、 TypeError が送出されます。

データベース API は 2 ダース近くの照合タイプをサポートしています。詳しいリ ファレンスは フィールド照合リファレンス を参照してく ださい。ここでは、よく使う照合タイプをいくつか示します:

exact

「厳密一致」です。例えば:

>>> Entry.objects.get(headline__exact="Man bites dog")

は、以下のような SQL を生成します:

SELECT ... WHERE headline = 'Man bits dog';

照合タイプを指定しなかった場合、つまり二重アンダースコアの入ったキー ワード引数を使わないかぎり、照合条件は exact とみなされます。

例えば、以下の二つの文は同じ意味です:

>>> Blog.objects.get(id__exact=14)  # Explicit form
>>> Blog.objects.get(id=14)         # __exact is implied

exact はよく使われるため、便宜的にこのような仕様にしています。

iexact

大小文字を区別しない一致です。以下のクエリ:

>>> Blog.objects.get(name__iexact="beatles blog")

は、 “Beatles Blog”, “beatles blog”, “BeAtlES blOG” といったタイト ルの Blog オブジェクトにマッチします。

contains

大小文字を区別する部分一致テストです。以下のクエリ:

Entry.objects.get(headline__contains='Lennon')

は、おおざっぱにいって以下の SQL に変換されます:

SELECT ... WHERE headline LIKE '%Lennon%';

'Today Lennon honored' には一致しますが、 'today lennon honored' には一致しないので注意してください。

大小文字を区別しない icontains もあります。

startswith, endswith
それぞれ、前方一致と後方一致です。大小文字を区別しない istartswithiendswith もあります。

ここに挙げたのはほんの一部です。全ての照合タイプの解説は フィールド照合タイプのリファレンス を参照してくださ い。

リレーションをまたいだ照合

Django では、背後で自動的に SQL JOIN を処理し、照合の際にリレーションを 「追跡」する、強力でありながら直感的な手段を提供しています。リレーションを またぐには、二重アンダースコアを使ってリレーションの張られたフィールドのフィー ルド名を指定します。リレーション間のスパンは、目的のフィールドに到達するま でいくらでも連鎖させられます。

以下の例では、 name'Beatles Blog' であるような BlogEntry エントリオブジェクト全てを取得します:

>>> Entry.objects.filter(blog__name__exact='Beatles Blog')

スパンは好きなだけ深く張れます。

リレーションのスパンは逆方向にも張れます。「逆方向の」リレーションを参照す るには、モデル名を小文字にした名前を使います。

以下の例では、 headline'Lennon' を含むような Entry を少なく とも一つ持つような全ての Blog オブジェクトを取得します:

>>> Blog.objects.filter(entry__headline__contains='Lennon')

複数のリレーションをまたいでフィルタを行っていて、中間のモデルにフィルタ条 件に合致する値が存在しないと、 Django はその値が空である (NULL が入って いる) だけの有意なオブジェクトとして扱います。つまり、エラーを送出しません。 例えば、以下のフィルタを考えましょう:

Blog.objects.filter(entry__author__name='Lennon')

(Author モデルは存在するものとします) entryauthor を空にし ていると、 Django は author が存在しないために name を辿れませんが、 エラーは送出せず、あたかも name も空であるかのように扱います。たいてい は、この仕様で期待通りに動きますが、 isnull を使うと、少し混乱をきたし ます。すなわち:

Blog.objects.filter(entry__author__name__isnull=True)

は、 authorname が空の Blog だけでなく、 entryauthor が空の Blog も返してしまうのです。後者のオブジェクトを返して 欲しくないのなら、以下のように書いてください:

Blog.objects.filter(entry__author__isnull=False,
        entry__author__name__isnull=True)

多値のリレーションをスパンする

ManyToManyFieldForeignKey の逆リレーションを使ってフィルタを行 う場合、 2 種類の異なるフィルタ方法があります。 Blog / Entry のリレー ション (Blog から Entry が 1 対多であるようなリレーション) を考えて みましょう。このようなモデルでは、例えばヘッドラインに ‘Lennon’ を含み、 かつ 2008 年に公開されたエントリを持つようなブログを検索したい場合があ るでしょう。あるいは、ヘッドラインに ‘Lennon’ を含むか、 あるいは 2008 年に公開されたエントリを含むようなブログの検索もあり得ます。複数のエン トリが一つの Blog に関連付けられているので、いずれのクエリも実行可能で すし、意味をなす場合があるでしょう。

ManyToManyField を使っている場合にも、 同じような状況が起こります。例えば、 Entrytags という名前の ManyToManyField があり、 “music”“band” というタグに関連付けられたエントリを検索したい場合や、 “music” タグのついた “public” 状態のエントリを取り出したい場合があるで しょう。

こうした状況をうまく扱うために、 Django には filter() および exclude() といった呼び出しがあります。 filter() に指定した条件は同時に適用され、 条件全てに一致する要素をフィルタします。 filter() を連続 して呼び出すと、通常はオブジェクトの集合により狭い制約をかけますが、多値リ レーションの場合、新たな制約は、前の filter() で絞り込んだリレーション 先をさらに絞り込むのではなく、前の filter() で絞り込んだリレーション先 にリンクしているリレーション元からリレーションを張られている全てのオブジェ クトに対して適用されてしまいます。

この話はちょっとややこしいので、例を挙げて解説しましょう。 ‘Lennon’ をヘッ ドラインに含み、 2008 年に公開されたエントリを含むブログを選択するには、以 下のように書きます:

Blog.objects.filter(entry__headline__contains='Lennon',
        entry__pub_date__year=2008)

一方、 ‘Lennon’ をヘッドラインに含むか、 あるいは 2008 年に公開された エンリを含むブログを検索するには、以下のように書きます:

Blog.objects.filter(entry__headline__contains='Lennon').filter(
        entry__pub_date__year=2008)

後者の例では、最初の filter() でクエリセットに制約をかけ、特定のエント リにリンクを持つブログだけを取り出しています。次の filter() では、取り 出した Blog オブジェクトから、 さらに 二つ目の条件に合うものに制約を かけています。従って、二つ目の filter() で制約をかけている対象の entry は、最初のフィルタで絞り込んだエントリと同じときもあれば、違うと きもあります。一つめの filter() でフィルタしているのはあくまでも Blog であって、 Entry ではないからです。

この挙動は、 exclude() にもあてはまります。 一つの exclude() に指定した条件は、 (多値リレーションの場合は) 各インスタンスに同時に適用されます。 filter()exclude() を連続して呼び出すと、 毎回違うリンク先集合を使ってフィルタを行ってしまう可能性があるのです。

フィルタはモデルのフィールドを参照できる

これまで与えられた例の中で、私たちはフィルタを同じモデルフィールドの バリューと比較してきました。しかし、モデルの値を同一のモデルの別のフィールドと 比較したい場合はどうしたらよいのでしょう?

Django は F() expressions をこの比較のために 提供しています。 F() のインスタンスはクエリの内側のモデルフィールドへの 参照として働きます。これらのは同じモデルインス タンスの異なる二つのフィールドを比較するクエリフィルターです。

例えば、これは pingbacks よりコメントが多い全てのブログエントリーのリスト を見つけるものです。 F() オブジェクトを使って、 pingback のカウント数 が参照できるようにすます。:

>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))

Django は追加や削除、かけ算、分割と剰余などの計算も F() オブジェクトで 使えるようにサポートしています。ブログエントリーの中で、 pingbacks よりコメン トが 二倍 以上あるものを抽出するには、クエリをこのようにします:

>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)

ブログエントリーの中で、 pingback 自体ののカウント数とコメント数よりも ブログエントリーの、エントリーのレーティングが小さい・少ないものにするには このようにクエリを書きます。:

>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))

また、 F() オブジェクト内で離れた関係を表記するために、ダブルアンダー スコア表記を用いることができます。ダブルアンダースコア付きの F() オブジェクトは、ひもづいているオブジェクトへのアクセスが必要な 時のために存在しています。例えば、作者の名前がブログの名前と同じ 全てのエントリーを取り出そうとしたとき、このようなクエリが発行できます。

>>> Entry.objects.filter(authors__name=F('blog__name'))
Django 1.3 で新たに登場しました: リリースノートを参照してください

date と date/time フィールドは、 timedelta オブジェクト で足したり引いたりできます。これは発行されてから三日以上経た全ての エントリーを抽出するクエリです:

>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))

pk 照合ショートカット

利便性のために、 Django には pk という照合形式があります。 pkprimary_key を表します。

Blog モデルの例では、主キーは id フィールドなので、以下の二つの文は 等価です:

>>> Blog.objects.get(id__exact=14) # 明示的な形式
>>> Blog.objects.get(id=14) # 暗黙で __exact を表す
>>> Blog.objects.get(pk=14) # pk は暗黙で id__exact を表す

pk__exact のクエリにしか使えないわけではありません。どのクエリ 用キーワードも pk と組み合わせてかまいません。 pk を使うと、モデル の主キーに対するクエリを表します:

# id が 1, 4 および 7 のブログエントリを取得する
>>> Blog.objects.filter(pk__in=[1,4,7])

# id > 14 の全てのブログエントリを取得する
>>> Blog.objects.filter(pk__gt=14)

pk による検索は join 越しでも行えます。 例えば、以下の二つの文は等価で す:

>>> Entry.objects.filter(blog__id__exact=3) # 明示的な形式
>>> Entry.objects.filter(blog__id=3)        # 暗黙で __exact を表す
>>> Entry.objects.filter(blog__pk=3)        # __pk は暗黙で __id__exact を表す

LIKE 文におけるパーセント記号とアンダースコアのエスケープ

LIKE を使う SQL 文になるようなフィールド照合メソッド (iexact, contains, icontains, startswith, istartswith, endswith, iendswith) では、 LIKE 文で使われる二つの特殊な文字、すなわちパーセ ント記号とアンダースコアを自動的にエスケープします。 (LIKE 文では、パー セント記号は任意の複数文字に対するワイルドカードを表し、アンダースコアは任 意の一文字に対するワイルドカードを表します。)

この機能によって、照合操作を直感的に行え、データベースの抽象化を守れます。 例えば、パーセント記号を含むようなエントリ全てを取得したければ、以下のよう にパーセント記号をそのまま使います:

>>> Entry.objects.filter(headline__contains='%')

Django はクオートの処理に気を配ってくれます。 SQL は以下のような感じになり ます:

SELECT ... WHERE headline LIKE '%\%%';

アンダースコアについても同じようなエスケープを行います。パーセント記号とア ンダースコアはいずれも透過的に処理されます。

キャッシュとクエリセット

データベースへのアクセスを最小限にとどめるため、 QuerySet 各々にはキャッシュがあります。 効率的なコードを書く上で、キャッシュのからくりを理解しておくのは重要なことです。

QuerySet が新たに生成された時点では、 キャッシュは空です。 QuerySet を 最初に評価したとき (すなわち、データベースへのクエリが最初に生じたとき)、 Django はクエリ結果をクエリセットオブジェクト内のキャッシュに保存し、明示的 にリクエストした結果だけ (例えば、 QuerySet‘s に対してイテレーション操作を する場合には、結果セットの最初の要素) を返します。それ以後は、 QuerySet‘ を再利用するとキャッシュ済みの結果を返します。

このキャッシュの挙動をよく覚えておいて下さい。というのも、 QuerySet を正しく扱わないと、 おもわぬところで手を噛まれるはめになるからです。例えば、以下の例では 二つの QuerySet を作成し、値を評価して、 すぐにクエリセットを捨ててしまっています:

>>> print [e.headline for e in Entry.objects.all()]
>>> print [e.pub_date for e in Entry.objects.all()]

そのため、全く同じデータベースクエリを二度実行し、データベースの負荷を倍加 させています。また、 Entry は二つのリクエストを処理する間にも追加された り削除されたりする可能性があるため、二つのリストには必ずしも同じデータベー スレコードが入っているとは限りません。

こうした問題を避けるには、 QuerySet を保存して再利用してください:

>>> queryset = Entry.objects.all()
>>> print [p.headline for p in queryset] # クエリセットを評価します。
>>> print [p.pub_date for p in queryset] # キャッシュの値を再利用します。

Q オブジェクトを使った複雑な照合

filter() などで複数の キーワード引数を指定してクエリを行うと、各々のキーワード引数の表す照合条件は 違いに “AND” で結ばれます。より複雑なクエリ (例えば OR を使ったクエリ) を 実行する必要がある場合には Q オブジェクトを使えます。

Q オブジェクト (django.db.models.Q) は、 複数のキーワード引数をカプセル化するために使われます。 キーワード引数は前述の 「フィールドの照合」で説明したものと同じです。

例えば、以下の Q オブジェクトは単一の LIKE クエリをカプセル化してい ます:

from django.db.models import Q
Q(question__startswith='What')

Q オブジェクトは &| といった演算子で組み合わせられます。二 つの Q オブジェクトを演算子で結ぶと、新たな Q オブジェクトを生成し ます。

例えば、以下の文は二つの question__startswith クエリを “OR” したものを 表す単一の Q オブジェクトを生成します:

Q(question__startswith='Who') | Q(question__startswith='What')

この Q オブジェクトは以下の WHERE 節と等価です:

WHERE question LIKE 'Who%' OR question LIKE 'What%'

Q オブジェクトを &| で組み合わせれば、好きなだけ複雑なクエ リ文を作成できます。丸括弧を使ったグルーピングも可能です。また、 ~ 演算 子を使えば Q オブジェクトの「否 (nagate)」を取れるので、例えば以下のよ うに、通常のクエリと否のクエリの積をとれます:

Q(question__startswith='Who') | ~Q(pub_date__year=2005)

キーワード引数をとる照合関数 ( filter() exclude() get() など) には、複数の Q を固定引数として (名前なしの引数として) 渡せます。複数の Q オブジェクトを照合関数に渡した場合、それらは互いに “AND” で結ばれます。 例えば:

Poll.objects.get(
    Q(question__startswith='Who'),
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)

は、だいたい以下のような SQL になります:

SELECT * from polls WHERE question LIKE 'Who%'
    AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')

照合関数は Q オブジェクトとキーワード引数を混ぜて使えます。照合関数に渡 した全ての引数は (キーワード引数も Q オブジェクトも) 互いに “AND” で結 ばれます。ただし、 Q を指定する場合はどのキーワード引数よりも前に指定せ ねばなりません。たとえば:

Poll.objects.get(
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
    question__startswith='Who')

は有効なクエリで、前の例と同じになりますが、以下の文:

# INVALID QUERY
Poll.objects.get(
    question__startswith='Who',
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))

は無効です。

See also

OR lookups examples の例には、 Q を使った Django のユニット テストがあります。

オブジェクトの比較

二つのモデルオブジェクトを比較するには、標準の Python 比較演算子、すなわち 二重等号符: == を使います。背後では二つのモデルオブジェクト間の主キー値 が比較されます。

上の Entry の例では、以下の二つの文は等価です:

>>> some_entry == other_entry
>>> some_entry.id == other_entry.id

モデルの主キーが id という名前でなくても問題はありません。どのような名 前であれ、比較は常に主キーを使って行われます。例えば、モデルの主キーのフィー ルド名が name であれば、以下の二つの文は等価です:

>>> some_obj == other_obj
>>> some_obj.name == other_obj.name

オブジェクトの削除

削除用のメソッドには、 便宜的に delete() という名前が付いてます。 このメソッドはオブジェクトをただちに削除し、戻り値を返しません:

e.delete()

複数のオブジェクトの一斉削除も可能です。 QuerySet には QuerySet() の全てのメンバを削除します。

例えば、 pub_date が 2005 年の Entry オブジェクトを全て削除するには 以下のようにします:

Entry.objects.filter(pub_date__year=2005).delete()

忘れてならないのは、この処理はできる限り SQL レベルで行われるため、 delete() メソッドが全てのインスタンスの delete() メソッドを呼ぶとは 限らないということです。モデルクラス上で delete() をカスタマイズしてい て、オブジェクトを削除するときに呼び出したいのなら、 QuerySetdelete() メソッドで一括削除するのではなく、直接 ( QuerySet の各要素を 取り出して逐次 delete() を呼ぶなどして) 「手動で」インスタンスを削除せ ねばなりません。

Django は、オブジェクトを削除する際に、通常 SQLでいう ON DELETE CASCADE 制約をエミュレートします。すなわち、 削除対象のオブジェクトを指すような外部キーを持つ全てのオブジェクトも 同時に削除されるのです。:

b = Blog.objects.get(pk=1)
# 次の命令は、 Blog と Blog を指す Entry 全てを削除してしまいます。
b.delete()
Django 1.3 で新たに登場しました: このカスケードの挙動は ForeignKeyon_delete 引数を通して変えることが出 来ます。

delete()QuerySet のメソッドにすぎず、 Manager 自体には公開 されていないので注意してください。これは誤って Entry.objects.delete() を実行して 全ての エントリを削除してしまわないようにするための安全機構で す。本当に全てのオブジェクトを削除 したい のなら、以下のように明示的に全 てのオブジェクトを表すクエリセットをリクエストしてください:

Entry.objects.all().delete()

モデルインスタンスをコピーする

モデルインスタンスをコピーする組み込みのメソッドは提供されていません、 ですが全てのフィールドとバリューを同一にした新しいインスタンスを簡単に作成する ことができます。最もシンプルなケースでは、 pkNone にすることができ ます。ブログの例を使うと:

blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # post.pk == 1

blog.pk = None
blog.save() # post.pk == 2

継承を使うと、もう少し複雑になってしまいます。 Blog のサブクラスについて 考えてみましょう。:

class ThemeBlog(Blog):
    theme = models.CharField(max_length=200)

django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme = 'python')
django_blog.save() # django_blog.pk == 3

継承の働きによって、 pkid を None にしなくてはいけません:

django_blog.pk = None
django_blog.id = None
django_blog.save() # django_blog.pk == 4

このプロセスはひもづいているオブジェクトをコピーしません。もし、関係性のあるも のもコピーしたいのならば、もう少し多めにコードを書かなければいけません。 EntryAuthor に many to many フィールドを持っている場合:

entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry.save()
entry.authors = old_authors # saves new many2many relations

複数のオブジェクトを一度に更新する

QuerySet が表現する全てのオブジェクトのある フィールドに特定の値を指定したいような場合があります。 update() メソッドを使えば、 この処理を実現できます。例えば:

# pub_date が 2007 の全エントリのヘッドラインを更新する
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')

この操作を行えるのは、リレーションフィールドでないフィールドか、 ForeignKey のフィールドのみです。また、フィールドに指定する値はハードコー ドされた Python の値でなければなりません (つまり、その時の何らかのフィール ド値は直接セットできません)。 ForeignKey のフィールドを更新するには、以下のように、 新たなモデルインスタンスを作成して渡します:

>>> b = Blog.objects.get(pk=1)

# 全てのエントリをこの blog に属させる
>>> Entry.objects.all().update(blog=b)

update() メソッドは (delete() と同様)、即座に適用され、値を返しませ ん。また、更新対象のクエリセットには、モデルの主のテーブル一つに対してし かアクセスできないという制限があります。フィルターは関係するテーブルに 使えますが、アップデートできるのはモデルのメインテーブルのカラムだけです。:

>>> b = Blog.objects.get(pk=1)

# この blog に属している全ての headline をアップデートします。
>>> Entry.objects.select_related().filter(blog=b).update(headline='Everything is the same')

update() メソッドは SQL 文に直接変換されるので注意してください。このメ ソッドは、フィールド値の直接更新を大量一括実行するためのものです。 モデルの save() メソッドを呼出さないので、 ( QuerySet の呼び出しに連動している) pre_savepost_save といった シグナルは発生しません。QuerySet 内の全ての要素に対して save() メソッドを適用したければ、 単に全要素に渡って save() を呼び出してください:

for item in my_queryset:
    item.save()

モデルの中の別フィールドの値によって別フィールドをアップデートするには F() objects も使えます。これは、特に 最新の値によってカウンタを増加させたい場合に使えます。例えば、 pingback のカウント数をブログのエントリごとに増加させるには:

>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)

しかしながら、 F() はフィルタ内と EXCLUDE 句とは 違うところもあり、 アップデートに F() オブジェクトを使った時には JOIN が使えません。 つまり、アップデートされるモデル内で参照できるフィールドのみを使えるという ことです。もし、 JOIN を F() オブジェクトで使おうとした場合、 FieldError が生じます。:

# THIS WILL RAISE A FieldError
>>> Entry.objects.update(headline=F('blog__name'))

生 SQL へのフォールバック

Django のデータベースマッパで扱うには複雑すぎる SQL 文を書かねばならないよ うな場合には、生の SQL 文実行モードを使えます。Django は生 SQL 文をクエリ とうる方法を二つ持っています。 素の SQL クエリを直接実行する を見てください。

生 raw-SQL 文実行モードの使い方としてお勧めなのは、そのようなクエリ文を実行 するモデルのカスタムメソッドやカスタムマネジャのメソッドを実装するというも のです。 Django はモデルレイヤでデータベースクエリを記述するように 何ら要求してはいません が、このアプローチをとることで、データアクセスのた めのロジックを一箇所にまとめられるので、コード組織化の観点からスマートにな ります。詳しい説明は topics-db-sql を参照してください。

最後に、 Django のデータベースレイヤは単にデータベースへの一つのインタフェー スに過ぎないということに注意しておきましょう。データベースには他のツールや プログラム言語、データベースフレームワークを介してアクセスできます。データ ベースについて Django 固有の何かがあるわけではないのです。