简体   繁体   中英

How to implement cross join in django for a count annotation

I present a simplified version of my problem. I have venues and timeslots and users and bookings, as shown in the model descriptions below. Time slots are universal for all venues, and users can book into a time slot at a venue up until the venue capacity is reached.

class Venue(models.Model):

    name = models.Charfield(max_length=200)
    capacity = models.PositiveIntegerField(default=0)


class TimeSlot(models.Model):

    start_time = models.TimeField()
    end_time = models.TimeField()


class Booking(models.Model):

    user = models.ForeignKey(User)
    time_slot = models.ForeignKey(TimeSlot)
    venue = models.ForeignKey(Venue)

Now I would like to as efficiently as possible get all possible combinations of Venues and TimeSlots and annotate the count of the bookings made for each combination, including the case where the number of bookings is 0.

I have managed to achieve this in raw SQL using a cross join on the Venue and TimeSlot tables. Something to the effect of the below. However despite exhaustive searching have not been able to find a django equivalent.

SELECT venue.name, timeslot.start_time, timeslot.end_time, count(booking.id)
FROM myapp_venue as venue
CROSS JOIN myapp_timeslot as timeslot
LEFT JOIN myapp_booking as booking on booking.time_slot_id = timeslot.id
GROUP BY venue.name, timeslot.start_time, timeslot.end_time

I'm also able to annotate the query to retrieve the count of bookings for which bookings for that combination do exist . But those combinations with 0 bookings get excluded. Example:

qs = Booking.objects.all().values(
            venue=F('venue__name'),
            start_time=F('time_slot__start_time'),
            end_time=F('time_slot__end_time')
        ).annotate(bookings=Count('id')) \
            .order_by('venue', 'start_time', 'end_time')

How can I achieve the effect of the CROSS JOIN query using the django ORM?

I don't believe Django has the capability to do cross joins without reverting down to raw SQL. I can give you two ideas that could point you in the right direction though:

  1. Combination of queries and python loops.

     venues = Venue.objects.all() time_slots = TimeSlot.objects.all() qs = ** your customer query above ** # Loop through both querysets, to create a master list. venue_time_slots = [] for venue in venues: for time_slot in time_slots: venue_time_slots.append(venue.name, time_slot.start_time, time_slot.end_time, 0) # Loop through master list and then compare to custom qs to update the count. for venue_time in venue_time_slots: for vt in qs: # Check if venue and time found. if venue_time[0] == qs.venue and venue_time[1] == qs.start_time: venue_time[3] += qs.bookings break
  2. The harder one which I don't have a solution is to use a combination of filter, exclude, and union. I only have used this with 3 tables (two parents with a child-link-table), where you have 4 including user. So I can only provide the logic and not an example.

     # Get all results that exist in table using .filter(). first_query.filter() # Get all results that do not exist by using .exclude(). # You can use your results from the first query to exclude also, but # would need to create an interim list. exclude_ids = [fq_row.id for fq_row in first_query] second_query.exclude(id__in=exclude_ids) # Combine both queries query = first_query.union(second_query) return query

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM