gpt4 book ai didi

python - Django 查询集优化 - 防止选择带注释的字段

转载 作者:行者123 更新时间:2023-12-02 16:47:42 24 4
gpt4 key购买 nike

假设我有以下模型:

class Invoice(models.Model):
...

class Note(models.Model):
invoice = models.ForeignKey(Invoice, related_name='notes', on_delete=models.CASCADE)
text = models.TextField()

我想选择有一些注释的发票。我会像这样使用 annotate/Exists 编写它:

Invoice.objects.annotate(
has_notes=Exists(Note.objects.filter(invoice_id=OuterRef('pk')))
).filter(has_notes=True)

这很好用,只过滤带有注释的发票。但是,此方法导致字段出现在查询结果中我不需要,这意味着性能较差(SQL 必须执行 2 次子查询)。

我意识到我可以使用 extra(where=) 来写这个像这样:

Invoice.objects.extra(where=['EXISTS(SELECT 1 FROM note WHERE invoice_id=invoice.id)'])

这会产生理想的 SQL,但通常不鼓励使用 extra/原始 SQL。有更好的方法吗?

最佳答案

您可以使用 .values() 查询集方法从 SELECT 子句中删除注释。 .values() 的问题是你必须枚举所有你想保留的名字而不是你想跳过的名字,.values() 返回字典而不是模型实例。

Django 内部跟踪删除的注解QuerySet.query.annotation_select_mask。所以你可以用它来告诉 Django,即使没有 .values() 也可以跳过哪些注释:

class YourQuerySet(QuerySet):
def mask_annotations(self, *names):
if self.query.annotation_select_mask is None:
self.query.set_annotation_mask(set(self.query.annotations.keys()) - set(names))
else:
self.query.set_annotation_mask(self.query.annotation_select_mask - set(names))
return self

然后你可以这样写:

invoices = (Invoice.objects
.annotate(has_notes=Exists(Note.objects.filter(invoice_id=OuterRef('pk'))))
.filter(has_notes=True)
.mask_annotations('has_notes')
)

从 SELECT 子句中跳过 has_notes 并仍然获得过滤的发票实例。生成的 SQL 查询类似于:

SELECT invoice.id, invoice.foo FROM invoice
WHERE EXISTS(SELECT note.id, note.bar FROM notes WHERE note.invoice_id = invoice.id) = True

请注意,annotation_select_mask 是内部 Django API,可以在未来版本中更改而不会发出警告。

关于python - Django 查询集优化 - 防止选择带注释的字段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59875799/

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