Django ORM-左外连接有两列? [英] Django ORM - LEFT OUTER JOIN with two columns?
问题描述
这是相关的代码:
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'), ]
现在,对于网站中的特定用户:
我要查询所有书籍(所有表格)。并且,
对于每个书本对象,如果用户拥有该书的BookNote,请获取该书,否则书本应为空。
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.
这是我将如何使用SQL做到这一点(有效):
This is how I would do that with SQL (works):
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))
我检查了 qs.query
,我明白了为什么-Django使用WHERE子句按user_id进行过滤,所以我没有得到所有的书。
如何使用django ORM进行相同的查询?没有原始sql?
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.
How can I do the same query with django ORM? Without raw 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.
我认为您要查找的内容最好作为注释。在django 2.0+下,您可以使用新的FilteredRelation来执行 LEFT OUTER JOIN ON(... AND ...)
,但是在执行和维护ORM中的ForeignKey;您必须重新导出需要的字段以及其他注释。
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; 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'),
)
结果查询:
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>))
如果您仍在使用1.11,则使用Prefetch对象或使用case-when注释可以获得相同的结果(但性能较低,查询有所不同)。
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.
在models.py中:
In 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'
)
由您查询:
q = Book.objects.all().prefetch_related(Book.usernote_prefetch(USER))
完整 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)
这篇关于Django ORM-左外连接有两列?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!