python - 使用有序相关模型的第一个值来注释QuerySet

原文 标签 python django postgresql django-queryset django-1.10

我有一些对象。对于每一个,我都希望用相关模型的最小值进行注释(在几个条件下结合,按日期排序)。我可以用SQL简洁地表达我想要的结果,但是我很好奇如何将其转换为Django的ORM。
背景
假设我有两个相关的模型:QuerySetBook,每个模型都有一个foreign key to anBlogPost

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中,这可以通过窗口化很好地实现。
PostgreSQL 9.6中的工作解决方案
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;

翻译成Django的ORM
虽然上面的SQL很适合我的需要(如果需要,我可以使用原始SQL),但是我很好奇在查询集中如何做到这一点。我有一个现有的查询集,我想在其中进一步注释它
books = models.Book.objects.filter(...).select_related(...).prefetch_related(...)
annotated_books = books.annotate(
    most_recent_title=...
)

我知道Django2.0支持窗口功能,但我现在使用的是Django1.10。
尝试的解决方案
我首先构建了一个Author对象来过滤在博客帖子之后发布的神秘书籍。
published_after = Q(
    author__book__date_published__gte=F('date_published'),
    author__book__genre='mystery'
)

从这里开始,我试图拼凑出我想要的结果,但是没有成功。
注意:Django2.0引入了窗口表达式,但我现在在Django1.10上,我想知道如何使用那里可用的查询集功能来实现这一点。

最佳答案

也许使用.raw不是一个坏主意。检查代码是否存在Window class我们可以看到,这实质上构成了一个SQL查询来实现“窗口化”。
一个简单的方法可能是使用architect模块,该模块可以根据the documentation为PostgreSQL添加分区功能。
另一个声称向django<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

最后,您可以复制项目中的类源代码或使用窗口类代码。

相关文章:

python - Celery Django-自定义任务发现

python - 如何创建Kivy文件(.kv)

python - 从相对时间pd.df列构造日期列

django - Django服务生成的Excel文件

python - 修复方法:Django错误:无此类表:main.classroom_student__old

python - Django查询过滤器多对多等等

sql - 如何将*大*数据块导入PostgreSQL?

python - 重命名字典键

postgresql - PostgreSQL-PostgreSQL中的datename()函数是否可以替代?

python - 加入两个时间序列的最有效方法