Getting into deep water with Subquery. I have a set of Carpark
s. Carpark
s have multiple Booking
s. Bookings have many BarrierActivity
records, which are the various coming and going events at the barriers. These are all simple FKs up the stack.
It is possible for a booking to arrive and the barrier cameras not recognise it. A member of staff will buzz them in but that means the system has failed for some reason. And that's what I'm trying to do here. Work out what percentage of my bookings got in via automated means. I know there are a number of other ways of doing this, but I'd like to do this with a single subquery-based queryset.
My aim reasonably simple. Annotate 0 or 1 to show whether or not an "entry" BarrierActivity
exists for each Booking
. Annotate an average of those values, per Carpark
.
The first part is fine. I can do a simple Exists()
between BarrierActivity
and Booking
and then each booking has the 0 or 1:
successful_bas = BarrierActivity.objects.order_by().filter(
booking=OuterRef('pk'),
activity_type=BarrierActivity.TYPE_ANPR_BOOKING,
direction='entry'
).values('booking')
Booking.objects.order_by().annotate(
entry_success=Exists(successful_bas)
)
And again, that works fine. But as soon as I try to scale that up another layer (so looking at Carpark
instead of Booking
)...
successful_bas = BarrierActivity.objects.order_by().filter(
booking=OuterRef('pk'),
activity_type=BarrierActivity.TYPE_ANPR_BOOKING,
direction='entry'
).values('booking')
bookings = Booking.objects.order_by().filter(
carpark=OuterRef('pk')
).values('carpark').annotate(
entry_success=Exists(successful_bas)
).values('entry_success')
Carpark.objects.order_by().annotate(
entry_hitrate=ExpressionWrapper(
Avg(Cast(Subquery(bookings), IntegerField())) * 100,
output_field=FloatField()
)
)
... I get the Subquery-error-of-doom: more than one row returned by a subquery used as an expression
. The bookings
subquery it clearly returning too much but how do I aggregate that before it hits the outermost subquery?
I've tried many things, but here's a reorganisation of the average to within the subquery. Same error:
successful_bas = "... as before ..."
bookings = Booking.objects_standard.order_by().filter(
carpark=OuterRef('pk')
).values('site').annotate(
entry_success=Exists(successful_bas)
).annotate(
entry_avg=Avg(Cast('entry_success', IntegerField()))
).values('entry_avg')
Carpark.objects.order_by().annotate(
entry_hitrate=ExpressionWrapper(
Subquery(bookings, output_field=FloatField()) * 100,
output_field=FloatField()
)
)