Skip to content

Instantly share code, notes, and snippets.

@bellini666
Last active April 19, 2024 15:41
Show Gist options
  • Select an option

  • Save bellini666/bc79bc85ba38741d056ac59019cf4e60 to your computer and use it in GitHub Desktop.

Select an option

Save bellini666/bc79bc85ba38741d056ac59019cf4e60 to your computer and use it in GitHub Desktop.
Django Aggregate Subquery
"""
This can be used to get the aggregated value of a field in a subquery, like:
>>> comments_subquery = Comment.objects.filter(
... post=OuterRef("pk"),
... )
>>> posts = Post.objects.annotate(
... most_recent_comment_date=MinSubquery(
... comments_subquery.values("created_at"),
... "created_at",
... )
... )
This would produce the following SQL:
SELECT
post.id,
(SELECT MIN(updated_at) FROM comment WHERE comment.post_id = post.id) as most_recent_comment_date
FROM post
"""
from typing import Any, Self, cast
from django.db import connection
from django.db.models.expressions import Subquery
class AggregateSubquery(Subquery):
agg_op: str
params: list[Any]
def __init__(self, queryset, field: str, *args, **kwargs):
self.agg_field = field
self.params = []
super().__init__(queryset, *args, **kwargs)
def copy(self):
clone = cast(Self, super().copy())
clone.agg_field = self.agg_field
clone.params = self.params
return clone
def as_sql(self, *args, **kwargs):
sql, sql_params = super().as_sql(*args, **kwargs)
variables = [f'__t."{self.agg_field}"', *self.params]
sql = f'(SELECT {self.agg_op}({", ".join(variables)}) FROM ({sql}) AS __t)'
return sql, sql_params
class CountSubquery(AggregateSubquery):
agg_op = "COUNT"
class SumSubquery(AggregateSubquery):
agg_op = "SUM"
class MinSubquery(AggregateSubquery):
agg_op = "MIN"
class MaxSubquery(AggregateSubquery):
agg_op = "MAX"
class AvgSubquery(AggregateSubquery):
agg_op = "AVG"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment