Django 左外部結合 質問する

Django 左外部結合 質問する

ユーザーが映画のリストを閲覧し、映画のレビューを作成できるウェブサイトを持っています。

ユーザーはすべての映画のリストを見ることができる必要があります。さらに、ユーザーが映画をレビューしている場合は、その映画に付けたスコアを見ることができる必要があります。そうでない場合は、スコアなしで映画が表示されます。

彼らは他のユーザーが提供するスコアをまったく気にしません。

次のことを考慮してくださいmodels.py

from django.contrib.auth.models import User
from django.db import models


class Topic(models.Model):
    name = models.TextField()

    def __str__(self):
        return self.name


class Record(models.Model):
    user = models.ForeignKey(User)
    topic = models.ForeignKey(Topic)
    value = models.TextField()

    class Meta:
        unique_together = ("user", "topic")

私が本質的に望んでいるのはこれです

select * from bar_topic
left join (select topic_id as tid, value from bar_record where user_id = 1)
on tid = bar_topic.id

test.pyコンテキストについては次の点を考慮してください。

from django.test import TestCase

from bar.models import *


from django.db.models import Q

class TestSuite(TestCase):

    def setUp(self):
        t1 = Topic.objects.create(name="A")
        t2 = Topic.objects.create(name="B")
        t3 = Topic.objects.create(name="C")
        # 2 for Johnny
        johnny = User.objects.create(username="Johnny")
        johnny.record_set.create(topic=t1, value=1)
        johnny.record_set.create(topic=t3, value=3)
        # 3 for Mary
        mary = User.objects.create(username="Mary")
        mary.record_set.create(topic=t1, value=4)
        mary.record_set.create(topic=t2, value=5)
        mary.record_set.create(topic=t3, value=6)

    def test_raw(self):
        print('\nraw\n---')
        with self.assertNumQueries(1):
            topics = Topic.objects.raw('''
                select * from bar_topic
                left join (select topic_id as tid, value from bar_record where user_id = 1)
                on tid = bar_topic.id
                ''')
            for topic in topics:
                print(topic, topic.value)

    def test_orm(self):
        print('\norm\n---')
        with self.assertNumQueries(1):
            topics = Topic.objects.filter(Q(record__user_id=1)).values_list('name', 'record__value')
            for topic in topics:
                print(*topic)

両方のテストでまったく同じ出力が印刷されるはずですが、生のバージョンのみが正しい結果の表を出力します。


---
1 1
B なし
C3 3 10 11 12 13 14 15 16 17 18 19 20 30 40 50 60 70 80 90 90 10 10 21 22 33 45 80 90 10 12 13 24 36 47 80 10 14 26 38 49 90 10

ormは代わりにこれを返します

オーム
---
1 1
C3 3 10 11 12 13 14 15 16 17 18 19 20 30 40 50 60 70 80 90 90 10 10 21 22 33 45 80 90 10 12 13 24 36 47 80 10 14 26 38 49 90 10

ユーザー「johnny」からのレビューがない残りのトピックを結合し直そうとすると、次の結果になります。

orm
---
A 1
A 4
B 5
C 3
C 6

Django ORM を使用して生のクエリの単純な動作を実現するにはどうすればよいですか?

編集: これはある程度機能しますが、非常に貧弱なようです:

トピック = Topic.objects.filter(record__user_id=1).values_list('name', 'record__value')
除外 = Topic.objects.exclude(record__user_id=1).values_list('name')
チェーン内のトピック(トピック、なし):
    ...

編集: これは少しは良くなりましたが、まだ悪いです:

    トピック = Topic.objects.filter(record__user_id=1).annotate(値=F('record__value'))
    トピック |= Topic.objects.exclude(pk__in=トピック)
オーム
---
1 1
B5 ...
C3 3 10 11 12 13 14 15 16 17 18 19 20 30 40 50 60 70 80 90 90 10 10 21 22 33 45 80 90 10 12 13 24 36 47 80 10 14 26 38 49 90 10

ベストアンサー1

まず第一に、表現を持つ方法はありません(現時点ではDjango 1.9.7)。DjangoのORMであなたが投稿した生のクエリのその通りご希望に応じてただし、次のような方法でも同じ結果を得ることができます。

>>> Topic.objects.annotate(
        f=Case(
            When(
                record__user=johnny, 
                then=F('record__value')
            ), 
            output_field=IntegerField()
        )
    ).order_by(
        'id', 'name', 'f'
    ).distinct(
        'id', 'name'
    ).values_list(
        'name', 'f'
    )
>>> [(u'A', 1), (u'B', None), (u'C', 3)]

>>> Topic.objects.annotate(f=Case(When(record__user=may, then=F('record__value')), output_field=IntegerField())).order_by('id', 'name', 'f').distinct('id', 'name').values_list('name', 'f')
>>> [(u'A', 4), (u'B', 5), (u'C', 6)]

最初のクエリに対して生成された SQL は次のとおりです。

>>> print Topic.objects.annotate(f=Case(When(record__user=johnny, then=F('record__value')), output_field=IntegerField())).order_by('id', 'name', 'f').distinct('id', 'name').values_list('name', 'f').query

>>> SELECT DISTINCT ON ("payments_topic"."id", "payments_topic"."name") "payments_topic"."name", CASE WHEN "payments_record"."user_id" = 1 THEN "payments_record"."value" ELSE NULL END AS "f" FROM "payments_topic" LEFT OUTER JOIN "payments_record" ON ("payments_topic"."id" = "payments_record"."topic_id") ORDER BY "payments_topic"."id" ASC, "payments_topic"."name" ASC, "f" ASC

##いくつかのメモ

  • 特にパフォーマンスが最優先の場合は、生のクエリを躊躇せずに使用します。ほとんど重要なことです。さらに、DjangoのORMを使用して同じ結果を得ることができない場合、それは必須です。他の場合には可能ですが、時にはきれいで理解しやすいコードを持つことがパフォーマンスよりも重要です。この作品コードの。
  • distinctこの回答では位置引数付きが使用されていますが、これは現時点ではPostgreSQLでのみ利用可能です。ドキュメントで詳細を確認できます。条件式

おすすめ記事