gpt4 book ai didi

python - Django ORM - 有两列的左外连接?

转载 作者:太空宇宙 更新时间:2023-11-04 09:45:11 24 4
gpt4 key购买 nike

这是相关代码:

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 - 获取它,否则 booknote 应该为空。

这就是我使用 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})

这是我试过的方法,有效:

qs = Book.objects.filter(Q(booknote__user_id=user_id_here) | Q(booknote__isnull=True))

我检查了 qs.query,我明白了为什么 - Django 使用 WHERE 子句按 user_id 过滤,所以我没有得到所有的书。
我怎样才能用 django ORM 做同样的查询?没有原始 sql?

最佳答案

您的查询不起作用的原因是您明确要求提供带有用户笔记的书籍或根本没有笔记:这不包括只有其他用户有笔记的书籍。

我认为您正在寻找的内容最好作为注释来执行。在 django 2.0+ 下,您可以使用新的 FilteredRelation 来执行 LEFT OUTER JOIN ON (... AND ...),但我在执行此操作以及在 ORM 中维护 ForeignKey 时遇到了麻烦;您必须使用附加注释重新导出所需的字段。

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 注释 获得相同的结果(但性能较低且查询不同)。

在 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)

关于python - Django ORM - 有两列的左外连接?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50106007/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com