次のモデルがあるとしましょう:
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
/ rawSQLを使用することはお勧めしません。これを行うためのより良い方法はありますか?
.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')
)
has_notes
SELECT句をスキップしても、フィルタリングされた請求書インスタンスを取得します。結果の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
は内部DjangoAPIであり、将来のバージョンで警告なしに変更される可能性があることに注意してください。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加