[英]Django ORM - LEFT OUTER JOIN with two columns?
This is the relevant code: 这是相关代码:
class Book(models.Model):
name = models.CharField(max_length=50)
class BookNote(models.Model):
text = models.CharField(max_length=50)
book = models.ForeignKey(Book)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
class Meta:
unique_together = [('book', 'user'), ]
Now, for a specific user in the website: 现在,对于网站中的特定用户:
I want to query all the books (all the table). 我想查询所有书籍(所有桌子)。 And,
和,
For each book object, If the user has a BookNote for the book - get it, otherwise booknote should be null. 对于每个书对象,如果用户具有该书的BookNote,请获取它,否则Booknote应该为null。
This is how I would do that with SQL (works): 这就是我使用SQL的方式(有效):
SELECT book.name, booknote.text
FROM book
LEFT OUTER JOIN booknote ON
(book.id = booknote.book_id AND booknote.user_id = {user_id_here})
This is what I've tried, does not work: 这是我试过, 不工作:
qs = Book.objects.filter(Q(booknote__user_id=user_id_here) | Q(booknote__isnull=True))
I examine qs.query
and I see why - Django uses WHERE clause to filter by user_id, so I don't get all the books. 我检查了
qs.query
然后了解了原因qs.query
使用WHERE子句按user_id进行过滤,因此我收不到所有的书。
How can I do the same query with django ORM? 如何使用django ORM执行相同的查询? Without raw sql?
没有原始SQL?
The reason your query doesn't work is you're expicitly asking for either Books with your user's notes or no notes at all: this excludes books where only other users have notes. 您的查询不起作用的原因是,您明确要求提供带有用户注释的书或根本没有注释的书:这不包括只有其他用户有注释的书。
I think what you're looking for is best performed as an annotation. 我认为您要寻找的内容最好作为注释执行。 Under django 2.0+, you can use the new FilteredRelation to perform a
LEFT OUTER JOIN ON (... AND ...)
, but I had trouble doing it and maintaining the ForeignKey in the ORM; 在django 2.0+下,您可以使用新的FilteredRelation来执行
LEFT OUTER JOIN ON (... AND ...)
,但是我很难做到并在ORM中维护ForeignKey。 you'll have to re-export the fields you need with additional annotations. 您必须重新导出需要的字段以及其他注释。
q = Book.objects.all().annotate(
usernote=FilteredRelation('booknote', condition=Q(booknote__user=USER_ID)),
usernote_text=F('usernote__text'),
usernote_id=F('usernote'),
)
Resulting query: 结果查询:
SELECT "books_book"."id", "books_book"."name", usernote."text" AS "usernote_text", usernote."id" AS "usernote_id" FROM "books_book" LEFT OUTER JOIN "books_booknote" usernote ON ("books_book"."id" = usernote."book_id" AND (usernote."user_id" = <USER_ID>))
If you're using 1.11 still, you can get the same result (but less performance and different queries) with Prefetch objects
or a case-when annotation
. 如果仍在使用1.11,则通过Prefetch对象
或case-when注释
可以获得相同的结果(但性能较低,查询不同)。
In models.py: 在models.py中:
class Book(models.Model):
# SNIP
@property
def usernote(self):
# raises an error if not prefetched
try:
return self._usernote[0] if self._usernote else None
except AttributeError:
raise Exception("Book.usernote must be prefetched with prefetch_related(Book.usernote_prefetch(user)) before use")
@staticmethod
def usernote_prefetch(user):
return Prefetch(
'booknote_set',
queryset=BookNote.objects.filter(user=user)
to_attr='_usernote'
)
By your query: 根据您的查询:
q = Book.objects.all().prefetch_related(Book.usernote_prefetch(USER))
Full tests.py
: 完整
tests.py
:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.test import TestCase
from django.db.models import *
from books.models import Book, BookNote
from django.contrib.auth import get_user_model
class BookTest(TestCase):
def setUp(self):
User = get_user_model()
self.u1 = User.objects.create(username="U1")
self.u2 = User.objects.create(username="U2")
self.b1 = Book.objects.create(name="B1") # Has no notes
self.b2 = Book.objects.create(name="B2") # Has a note for U1 and U2
self.b3 = Book.objects.create(name="B3") # Has a note for just U2
self.n1 = BookNote.objects.create(text="N1", book=self.b2, user=self.u1)
BookNote.objects.create(text="N2", book=self.b2, user=self.u2)
BookNote.objects.create(text="N3", book=self.b1, user=self.u2)
def test_on_multiple(self):
q = Book.objects.all().annotate(
usernote=FilteredRelation('booknote', condition=Q(booknote__user=self.u1)),
usernote_text=F('usernote__text'),
usernote_id=F('usernote'),
).order_by('id')
print(q.query)
self.assertEqual(q.count(), Book.objects.count())
self.assertIsNone(q[0].usernote_text)
self.assertEqual( q[1].usernote_text, self.n1.text)
self.assertIsNone(q[2].usernote_text)
def test_on_multiple_prefetch(self):
@property
def usernote(self):
return self._usernote[0] if self._usernote else None
Book.usernote = usernote
q = Book.objects.all().prefetch_related(Prefetch(
'booknote_set',
queryset=BookNote.objects.filter(user=self.u1),
to_attr='_usernote'
)).order_by('id')
self.assertEqual(q.count(), Book.objects.count())
self.assertIsNone(q[0].usernote)
self.assertEqual( q[1].usernote.text, self.n1.text)
self.assertIsNone(q[2].usernote)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.