In my app, I can describe an Entity using different Protocols, with each Protocol being a collection of various Traits, and each Trait allows two or more Classes. So, a Description is a collection of Expressions. E.g., I want to describe an entity "John" with the Protocol "X" that comprises the following two Traits and Classes:
Protocol ABC
Trait 1: Height
Available Classes: a. Short b. Medium c. Tall
Trait 2: Weight
Available Classes: a. Light b. Medium c. Heavy
John's Description: Expression 1: c. Tall, Expression 2: b. Medium
My model specification (barebone essentials for simplicity):
class Protocol(models.Model):
"""
A Protocol is a collection of Traits
"""
name = models.CharField()
class Trait(models.Model):
"""
Stores the Traits. Each Trait can have multiple Classes
"""
name = models.CharField()
protocol = models.ForeignKey(
Protocol,
help_text="The reference protocol of the trait",
)
class Class(models.Model):
"""
Stores the different Classes related to a Trait.
"""
name = models.CharField()
trait = models.ForeignKey(Trait)
class Description(models.Model):
"""
Stores the Descriptions. A description is a collection of Expressions.
"""
name = models.CharField()
protocol = models.ForeignKey(
Protocol,
help_text="reference to the protocol used to make the description;\
this will define which Traits will be available",
)
entity = models.ForeignKey(
Entity,
help_text="the Entity to which the description refers to",
)
class Expression(models.Model):
"""
Stores the expressions of entities related to a specific
Description. It refers to one particular Class (which is
then associated with a specific Trait)
"""
class = models.ForeignKey(Class)
description = models.ForeignKey(Description)
Following the previous example, let's say I want to find all the Entities that are medium or tall (Trait 1) and heavy (Trait 2). The query I'm now using is the following:
# This is the filter returned by the HTML form, which list
# all the available Classes for each Trait of the selected Protocol
filters = [
{'trait': 1, 'class': [2, 3]},
{'trait': 2, 'class': [6,]},
]
queryset = Description.objects.all()
for filter in filters:
queryset = queryset.filter(expression_set__class__in=filter["class"])
The problem is that the query is slow (I have ATM ~1000 Descriptions, described with
a Protocol of 40 Traits, each Trait having 2 to 5 Classes). It takes about two
seconds to return the results even when filtering by only 5-6 Expressions.
I tried using prefetch_related("expression_set")
or
prefetch_related("expression_set__class")
but with no significant improvement.
The question is: can you suggest a way to improve the performance, or this is simply the reality of searching through so many tables?
Thank you very much for your time.
EDIT: The following is the query generated by the Manager when, e.g., eight filters (see previous code snippet) are applied.
SELECT "describe_description"."id",
"describe_description"."name",
"describe_description"."protocol_id",
FROM "describe_description"
INNER JOIN "describe_expression"
ON ("describe_description"."id" = "describe_expression"."description_id")
INNER JOIN "describe_expression" T4
ON ("describe_description"."id" = T4."description_id")
INNER JOIN "describe_expression" T6
ON ("describe_description"."id" = T6."description_id")
INNER JOIN "describe_expression" T8
ON ("describe_description"."id" = T8."description_id")
INNER JOIN "describe_expression" T10
ON ("describe_description"."id" = T10."description_id")
INNER JOIN "describe_expression" T12
ON ("describe_description"."id" = T12."description_id")
INNER JOIN "describe_expression" T14
ON ("describe_description"."id" = T14."description_id")
INNER JOIN "describe_expression" T16
ON ("describe_description"."id" = T16."description_id")
INNER JOIN "describe_expression" T18
ON ("describe_description"."id" = T18."description_id")
WHERE ("describe_expression"."class_id" IN (732) AND T4."class_id" IN (740) AND T6."class_id" IN (760) AND T8."class_id" IN (783) AND T10."class_id" IN (794) AND T12."class_id" IN (851) AND T14."class_id" IN (857) AND T16."class_id" IN (860) AND T18."class_id" IN (874))