我有一些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功能来实现这一点。
答案 0 :(得分:4)
也许使用.raw
并不是一个坏主意。检查Window
class的代码,我们可以看到基本上组成了一个SQL查询来实现" Windowing"。
一个简单的方法可能是使用architect模块,它可以根据the documentation为PostgreSQL添加分区功能。
另一个声称向Django注入Window功能的模块< 2.0是django-query-builder,它添加了partition_by()
查询集方法,可以与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
类源代码,或使用this alternate Window类代码。
答案 1 :(得分:3)
你明显的问题是Django 1.10太旧而无法正确处理window functions(已经有很长时间非常了)。
如果你在没有窗口函数的情况下重写查询,那问题就会消失。
其中哪一个最快取决于可用的索引和数据分布。但是每一个都应该比你原来的更快。
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服务器的。