4

私はいつも、Django orm のサブクラス化モデルの処理がかなり気の利いたものであることに気付きました。それがおそらく、このような問題に遭遇する理由です。

3 つのモデルを取り上げます。

class A(models.Model):
    field1 = models.CharField(max_length=255)

class B(A):
    fk_field = models.ForeignKey('C')

class C(models.Model):
    field2 = models.CharField(max_length=255)

これで、モデルにクエリを実行して、可能な場合Aはすべてのモデルを取得Bできます。

the_as = A.objects.all()
for a in the_as:
    print a.b.fk_field.field2 #Note that this throws an error if there is no B record

これに関する問題は、すべてのデータを取得するために膨大な数のデータベース呼び出しを見ていることです。

ここで、データベース内のすべてのモデルの QuerySet を取得する必要があるとしAます。ただし、すべてのサブクラス レコードとサブクラスの外部キー レコードも取得し、 を使用select_related()してアプリケーションを単一のデータベース呼び出しに制限します。次のようなクエリを記述します。

the_as = A.objects.select_related("b", "b__fk_field").all()

1 つのクエリで、必要なすべてのデータが返されます。素晴らしい。

そうでないことを除いて。このバージョンのクエリは独自のフィルタリングを行っているため、select_related結果をまったくフィルタリングすることは想定されていません。

set_1 = A.objects.select_related("b", "b__fk_field").all() #Only returns A objects with associated B objects
set_2 = A.objects.all() #Returns all A objects
len(set_1) > len(set_2) #Will always be False

django-debug-toolbar を使用してクエリを調べたところ、問題が見つかりました。生成された SQL クエリは、他のサブクラス化されたフィールドのように、 を使用してテーブルをクエリINNER JOINに結合します。CLEFT OUTER JOIN

SELECT "app_a"."field1", "app_b"."fk_field_id", "app_c"."field2"
FROM "app_a" 
    LEFT OUTER JOIN "app_b" ON ("app_a"."id" = "app_b"."a_ptr_id") 
    INNER JOIN "app_c" ON ("app_b"."fk_field_id" = "app_c"."id");

そして、単純に を に変更すると、必要なレコードが得られるように見えINNER JOINますLEFT OUTER JOINが、Django の ORM を使用している場合は役に立ちません。

select_related()これは Django の ORM のバグですか? これを回避する方法はありますか? それとも、データベースに直接クエリを実行して結果を自分でマップする必要がありますか? これを行うには、Django-Polymorphic のようなものを使用する必要がありますか?

4

2 に答える 2

2

バグのように見えます。具体的には、A->B 関係の nullable な性質を無視しているようです。たとえば、サブクラス化の代わりに A で B への外部キー参照があった場合、その外部キーはもちろん nullable になり、 django は、左結合を使用します。おそらくこれを django issue tracker で提起する必要があります。問題を回避する可能性のある select_related の代わりに prefetch_related を使用することもできます。

于 2012-12-11T16:47:59.980 に答える
0

これに対する回避策を見つけましたが、より良い答えが得られることを期待して、それを受け入れるまでしばらく待ちます。

結果がデータベース内のレコードによってフィルタリングされないように、基になる SQL からINNER JOIN作成されたselect_related('b__fk_field')を削除する必要があります。したがって、新しいクエリではパラメーターBをそのままにしておく必要があります。b__fk_fieldselect_related

the_as = A.objects.select_related('b')

Cただし、これにより、オブジェクトからオブジェクトにアクセスするたびにデータベースを呼び出す必要がありAます。

for a in the_as:
    #Note that this throws an DoesNotExist error if a doesn't have an
    #associated b
    print a.b.fk_field.field2 #Hits the database everytime.

これを回避するハックはC、データベースから必要なすべてのオブジェクトを 1 つのクエリから取得し、各Bオブジェクトがそれらを手動で参照するようにすることです。Bこれを行うことができるのは、取得したオブジェクトにアクセスするデータベース呼び出しにはfk_field_id、関連付けられたCオブジェクトを参照する があるためです。

c_ids = [a.b.fk_field_id for a in the_as] #Get all the C ids
the_cs = C.objects.filter(pk__in=c_ids) #Run a query to get all of the needed C records
for c in the_cs:
    for a in the_as:
        if a.b.fk_field_id == c.pk: #Throws DoesNotExist if no b associated with a
            a.b.fk_field = c
            break

ネストされたループを使わずにそれを書く機能的な方法があると確信していますが、これは何が起こっているかを示しています。これは理想的ではありませんが、データベース ヒットの絶対最小数ですべてのデータを提供します。これが私が望んでいたことです。

于 2012-06-14T19:05:12.860 に答える