dimanche 18 mai 2014

python - Django compliqué annotation avec référence auto - Stack Overflow


Given the two models:



  • Floor that has a reference to itself called previousFloor which can be null for the first Floor

  • Room which of course can only be in one Floor (but there are multiple rooms in a Floor)


I need a query that returns all rooms on the top floor.




In order to achieve that, I created a function in the Floor model called get_next_floor which would return the Floor with previousFloor set to self and return None if the filter was empty (this is the last Floor). So another function in the Floor model called is_last_floor, would return True if get_next_floor returned None, and otherwise would returned False.


In the end I would loop through all the rooms and have to call is_last_floor for the Floor the Room is in, in order to decide if it fits my condition.


This problem can be slightly optimized by saving the already found last Floor during the loop.


Since this is only an abstraction of my real problem and I am working on a large database, this solution is no longer a viable option because of the bad performance of the loop.


Is there a way to implement this in one query with annotation?




A floor which is the top Floor is surely one which has no other Floor referring to it as previousFloor.


So:


top_floors = Floor.objects.exclude(id__in=Floor.objects.filter(previous_floor__isnull=False).values_list('previous_floor', flat=True))

which translates to:


SELECT "core_floor"."id", "core_floor"."name", "core_floor"."previous_floor_id" FROM "core_floor" WHERE NOT ("core_floor"."id" IN (SELECT U0."previous_floor_id" FROM "core_floor" U0 WHERE U0."previous_floor_id" IS NOT NULL))

HOWEVER MySQL is not great at nested queries so it may be more efficient to do:


lower_floors = Floor.objects.filter(previous_floor__isnull=False).values_list('previous_floor', flat=True)
top_floors = Floor.objects.exclude(id__in=list(covered_floors))

See https://docs.djangoproject.com/en/dev/ref/models/querysets/#in


Then to get rooms on top floors:


Room.objects.filter(floor__in=top_floors)


Given the two models:



  • Floor that has a reference to itself called previousFloor which can be null for the first Floor

  • Room which of course can only be in one Floor (but there are multiple rooms in a Floor)


I need a query that returns all rooms on the top floor.




In order to achieve that, I created a function in the Floor model called get_next_floor which would return the Floor with previousFloor set to self and return None if the filter was empty (this is the last Floor). So another function in the Floor model called is_last_floor, would return True if get_next_floor returned None, and otherwise would returned False.


In the end I would loop through all the rooms and have to call is_last_floor for the Floor the Room is in, in order to decide if it fits my condition.


This problem can be slightly optimized by saving the already found last Floor during the loop.


Since this is only an abstraction of my real problem and I am working on a large database, this solution is no longer a viable option because of the bad performance of the loop.


Is there a way to implement this in one query with annotation?



A floor which is the top Floor is surely one which has no other Floor referring to it as previousFloor.


So:


top_floors = Floor.objects.exclude(id__in=Floor.objects.filter(previous_floor__isnull=False).values_list('previous_floor', flat=True))

which translates to:


SELECT "core_floor"."id", "core_floor"."name", "core_floor"."previous_floor_id" FROM "core_floor" WHERE NOT ("core_floor"."id" IN (SELECT U0."previous_floor_id" FROM "core_floor" U0 WHERE U0."previous_floor_id" IS NOT NULL))

HOWEVER MySQL is not great at nested queries so it may be more efficient to do:


lower_floors = Floor.objects.filter(previous_floor__isnull=False).values_list('previous_floor', flat=True)
top_floors = Floor.objects.exclude(id__in=list(covered_floors))

See https://docs.djangoproject.com/en/dev/ref/models/querysets/#in


Then to get rooms on top floors:


Room.objects.filter(floor__in=top_floors)

0 commentaires:

Enregistrer un commentaire