I’m a beginner Django and I have been stuck on the following problem for a while.
The basic thing I would like to achieve is that when client makes a GET list api request with a time period parameter (say 3 months) then the server would return an aggregation of the current 3 months data and also show an extra field comparing the difference with previous 3 months.
I have models as:
JavaScript
x
13
13
1
class NTA(models.Model):
2
nta_code = models.CharField(max_length=30, unique=True)
3
4
5
6
class NoiseComplaints(models.Model):
7
complaint_id = models.IntegerField(unique=True)
8
created_date = models.DateTimeField()
9
nta = models.ForeignKey(
10
NTA, on_delete=models.CASCADE, to_field='nta_code')
11
12
13
The sample output I would like to get is something like:
JavaScript
1
10
10
1
[
2
{
3
"id": 1,
4
"nta_code": "New York",
5
"noise_count_current": 30, # this would be current 3m count of NoiseData
6
"noise_count_prev": 20, # this would be prev 3m count of NoiseData
7
"noise_count_diff": 10, # this would be prev 3m count of NoiseData
8
}
9
10
The raw SQL query (in Postgres) is quite simple as below but I’m really struggling on how to make this work in Django.
JavaScript
1
34
34
1
WITH curr AS
2
(
3
SELECT "places_nta"."id", "places_nta"."nta_code",
4
COUNT("places_noisecomplaints"."id") AS "noise_count_curr"
5
FROM "places_nta"
6
INNER JOIN "places_noisecomplaints"
7
ON ("places_nta"."nta_code" = "places_noisecomplaints"."nta_id")
8
WHERE "places_noisecomplaints"."created_date"
9
BETWEEN '2022-03-31 00:00:00+00:00' AND '2022-06-30 00:00:00+00:00'
10
GROUP BY "places_nta"."id"
11
),
12
13
prev AS
14
(
15
SELECT "places_nta"."id", "places_nta"."nta_code",
16
COUNT("places_noisecomplaints"."id") AS "noise_count_prev"
17
FROM "places_nta"
18
INNER JOIN "places_noisecomplaints"
19
ON ("places_nta"."nta_code" = "places_noisecomplaints"."nta_id")
20
WHERE "places_noisecomplaints"."created_date"
21
BETWEEN '2022-01-01 00:00:00+00:00' AND '2022-03-31 00:00:00+00:00'
22
GROUP BY "places_nta"."id"
23
)
24
25
SELECT
26
curr.id,
27
curr.nta_code,
28
noise_count_curr - COALESCE(noise_count_prev, 0) AS noise_count_diff,
29
noise_count_curr,
30
noise_count_prev
31
FROM curr
32
LEFT JOIN prev
33
ON curr.id = prev.id
34
What I have Tried
- I don’t think using the raw query (in the form I indicated above) as works in my case b/c I need it to handle filters (the GET request from client can have other query params from which server will handle additional filters)
- I tried to union the current and previous querysets and then groupby, but it seems that this is not supported:
JavaScript
1
14
14
1
qs1 = queryset.filter(noisecomplaints__created_date__range=["2022-03-31", "2022-06-30"]).annotate(
2
noise_count=Count('noisecomplaints'),
3
tag=models.Value("curr", output_field=models.CharField()),
4
)
5
qs2 = queryset.filter(noisecomplaints__created_date__range=["2022-01-01", "2022-03-31"]).annotate(
6
noise_count=Count('noisecomplaints'),
7
tag=models.Value("prev", output_field=models.CharField()),
8
)
9
qs_union = qs1.union(qs2, all=True)
10
qs_result = qs_union.values('id').annotate(
11
noise_count_curr=Sum('noise_count', filter=Q(tag='curr')),
12
noise_count_prev=Sum('noise_count', filter=Q(tag='prev')),
13
)
14
django.db.utils.NotSupportedError: Calling QuerySet.annotate() after union() is not supported.
Advertisement
Answer
You could try this one.
JavaScript
1
14
14
1
from django.db.models import Case, When, IntegerField, F, Count
2
3
q = queryset.values('id').annotate(
4
noise_count_current= Count(Case(
5
When(noisecomplaints__created_date__range=["2022-03-31", "2022-06-30"], then=1),
6
output_field=IntegerField()
7
)),
8
noise_count_prev= Count(Case(
9
When(noisecomplaints__created_date__range=["2022-01-01", "2022-03-31"], then=1),
10
output_field=IntegerField()
11
)),
12
noise_count_diff = F('noise_count_current') - F('noise_count_prev')
13
)
14