[英]Annotate QuerySet with first value of ordered related model
我有一些對象的QuerySet
。 對於每一個,我希望用相關模型的最小值進行注釋(在幾個條件下加入,按日期排序)。 我可以在SQL中巧妙地表達我想要的結果,但很好奇如何翻譯成Django的ORM。
假設我有兩個相關的模型: Book
和BlogPost
,每個模型都有一個Author
的外鍵:
class Book(models.Model):
title = models.CharField(max_length=255)
genre = models.CharField(max_length=63)
author = models.ForeignKey(Author)
date_published = models.DateField()
class BlogPost(models.Model):
author = models.ForeignKey(Author)
date_published = models.DateField()
我試圖找到一個給定作者在他們寫的每篇博文之后發表的第一本神秘書。 在SQL中,這可以通過窗口很好地實現。
WITH ordered AS (
SELECT blog_post.id,
book.title,
ROW_NUMBER() OVER (
PARTITION BY blog_post.id ORDER BY book.date_published
) AS rn
FROM blog_post
LEFT JOIN book ON book.author_id = blog_post.author_id
AND book.genre = 'mystery'
AND book.date_published >= blog_post.date_published
)
SELECT id,
title
FROM ordered
WHERE rn = 1;
雖然上面的SQL很適合我的需求(如果需要我可以使用原始SQL),我很好奇在QuerySet中如何做到這一點。 我有一個現有的QuerySet,我想進一步注釋它
books = models.Book.objects.filter(...).select_related(...).prefetch_related(...)
annotated_books = books.annotate(
most_recent_title=...
)
我知道Django 2.0支持窗口函數,但我現在在Django 1.10上。
我首先構建了一個Q
對象來過濾到博客文章后發布的神秘書籍。
published_after = Q(
author__book__date_published__gte=F('date_published'),
author__book__genre='mystery'
)
從這里開始,我試圖拼湊django.db.models.Min
和其他F
對象來實現我想要的結果,但沒有成功。
注意:Django 2.0引入了窗口表達式,但我目前正在使用Django 1.10,並且很好奇如何使用那里提供的QuerySet功能。
也許使用.raw
並不是一個壞主意。 檢查Window
類的代碼,我們可以看到它實際上組成了一個SQL查詢來實現“窗口化”。
一個簡單的方法可能是使用架構師模塊,它可以根據文檔為PostgreSQL添加分區功能。
另一個聲稱向Django <2.0注入Window功能的模塊是django-query-builder ,它添加了partition_by()
queryset方法,可以與order_by
一起使用:
query = Query().from_table( Order, ['*', RowNumberField( 'revenue', over=QueryWindow().order_by('margin') .partition_by('account_id') ) ] ) query.get_sql() # SELECT tests_order.*, ROW_NUMBER() OVER (PARTITION BY account_id ORDER BY margin ASC) AS revenue_row_number # FROM tests_order
最后,您始終可以在項目中復制Window
類源代碼或使用此備用 Window類代碼。
你明顯的問題是Django 1.10太舊了,無法正常處理窗口函數 (已經存在了很長時間)。
如果您在沒有窗口函數的情況下重寫查詢,那么問題就會消失。
其中哪一個最快取決於可用的索引和數據分布。 但是每一個都應該比你原來的更快。
1.使用DISTINCT ON
:
SELECT DISTINCT ON (p.id)
p.id, b.title
FROM blog_post p
LEFT JOIN book b ON b.author_id = p.author_id
AND b.genre = 'mystery'
AND b.date_published >= p.date_published
ORDER BY p.id, b.date_published;
相關,詳細說明:
2.使用LATERAL
子查詢 (需要Postgres 9.3或更高版本):
SELECT p.id, b.title
FROM blog_post p
LEFT JOIN LATERAL (
SELECT title
FROM book
WHERE author_id = p.author_id
AND genre = 'mystery'
AND date_published >= p.date_published
ORDER BY date_published
LIMIT 1
) b ON true;
-- ORDER BY p.id -- optional
相關,詳細說明:
3.或者更簡單,但是,使用相關的子查詢 :
SELECT p.id
,(SELECT title
FROM book
WHERE author_id = p.author_id
AND genre = 'mystery'
AND date_published >= p.date_published
ORDER BY date_published
LIMIT 1)
FROM blog_post p;
-- ORDER BY p.id -- optional
每個都應該很容易翻譯成Django語法。 您也可以只使用原始SQL,無論如何都是發送到Postgres服務器的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.