I have a QuerySet
of some objects. For each one, I wish to annotate with the minimum value of a related model (joined on a few conditions, ordered by date). I can express my desired results neatly in SQL, but am curious how to translate to Django's ORM.
Background
Let's say that I have two related models: Book
, and BlogPost
, each with a foreign key to an 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()
I'm trying to find the first mystery book that a given author published after each blog post that they write. In SQL, this can be achieved nicely with windowing.
Working solution in 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;
Translating to Django's ORM
While the above SQL suits my needs well (and I could use raw SQL if needed), I'm curious as to how one would do this in QuerySet. I have an existing QuerySet where I'd like to annotate it even further
books = models.Book.objects.filter(...).select_related(...).prefetch_related(...)
annotated_books = books.annotate(
most_recent_title=...
)
I'm aware that Django 2.0 supports window functions, but I'm on Django 1.10 for now.
Attempted solution
I'd first built a Q
object to filter down to mystery books published after the blog post.
published_after = Q(
author__book__date_published__gte=F('date_published'),
author__book__genre='mystery'
)
From here, I attempted to piece together django.db.models.Min
and additional F
objects to acheive my desired results, but with no success.
Note: Django 2.0 introduces window expressions, but I'm currently on Django 1.10, and curious how one would do this with the QuerySet features available there.
annotate(first=Min(<related_model__id>)).filter(<conditions with timestamps>)
? – Eteocles