Requêtage complexe avec Django

Le problème exposé dans ce sujet a été résolu.

Bonjour !

Je reviens avec un problème que je n’arrive pas à résoudre sans faire exploser le nombre de requêtes à ma BDD.

Voici le contexte : pour mon site (d’organisation de tournois), il y a un système de ronde suisse où à chaque ronde, on fait s’affronter des adversaires (représentés ici par des SideBySideSubscription) appariés ensemble (les appariements étant représentés par des SideBySidePairing).

Voici synthétiquement les deux modèles :

class SideBySideSubscription(models.Model):
    team = models.ForeignKey("teams.Team", on_delete=models.CASCADE)
    tournament = models.ForeignKey(SideBySideTournament, on_delete=models.CASCADE)
  

class SideBySidePairing(models.Model):
    round_number = models.PositiveSmallIntegerField()
    party_1 = models.ForeignKey(
        SideBySideSubscription, on_delete=models.CASCADE, related_name="party_1"
    )
    party_2 = models.ForeignKey(
        SideBySideSubscription,
        on_delete=models.CASCADE,
        related_name="party_2",
        null=True,
        blank=True,
    )
    tournament = models.ForeignKey(SideBySideTournament, on_delete=models.CASCADE)

Note : le champ party_2 peut être vide car le nombre de participants peut être impair.

A chaque ronde, je vérifie la validité de celle-ci pour éviter les erreurs humaines. Une des vérifications que je fais est d’être sûr que chaque SideBySideSubscription n’est rattachée qu’à un et un seul SideBySidePairing.

Pour ce faire, je parcours actuellement manuellement via une boucle :

for subscription in accepted_subscriptions:
    pairings_for_round_ct = (
        pairings.filter(party_1_id=subscription["id"]).count()
        + pairings.filter(party_2_id=subscription["id"]).count()
    )
    if pairings_for_round_ct > 1:
        errors.append(
            _("%(team_name)s is in more than one pairing")
            % {"team_name": subscription["team__name"]}
        )
    if pairings_for_round_ct == 0:
        errors.append(
            _("%(team_name)s is not in the pairings")
            % {"team_name": subscription["team__name"]}
        )

Note encore : j’appelle les propriétés des objets via des dictionnaires car j’utilise .values() plus haut.

C’est fonctionnel, mais très inefficace, et bien sûr ça grossit avec le nombre de SideBySideSubscription, qui peut atteindre 600.

Du coup, j’aimerais trouver un moyen plus efficace de faire cela. Idéalement, en une requête, me renvoyer combien d’entités SideBySidePairing sont liées à une SideBySideSubscription donnée.

Malheureusement je ne vois pas du tout comment faire cela. Quelqu’un pourrait-il m’aider ?

Salut,

Sur ton modèle, tu peux aussi définir des valeurs qui doivent être uniques, ou des ensembles de valeurs qui doivent être uniques. Tu pourrais donc définir party_1 comme unique, et dans ce cas la BDD refusera l’entrée si un doublon est ajouté.

Edit : d’ailleurs, tu peux aussi définir ta relation en one-to-one

Sinon, tu as des liens dans les deux sens entre tes 2 tables : je pense que c’est une mauvaise pratique et que tu devrais simplifier ton modèle.

+0 -0

Hello,

Après avoir réfléchi, je pense que le mieux serait que la relation se fasse du côté des Subscriptions, puisque c’est une relation one-to-many. Tu pourrais ensuite rajouter un validateur avec Django, pour qu’à l’insertion il vérifie que la clé étrangère n’existe pas déjà 2 fois. De cette manière, tu es certain de ne pas insérer de mauvaise valeur.

En parallèle, tu devrais peut-être garantir que l’erreur de saisie manuelle ne soit pas possible au niveau du formulaire d’entrée, mais sans détail sur la façon dont s’est fait, je ne peux pas y réfléchir plus que ça.

Par ailleurs, je voulais juste t’indiquer que Django permet de faire des requêtes du type prefetch. Si tu fais ça, il chargera la table Subscription et la table Pairing en faisant une jointure entre les deux. De cette manière, il ne fera qu’une seule requête sur la BDD. Ça a le défaut qu’il garde tout en mémoire, donc sur une grande table ça peut représenter une charge. Mais 600 enregistrements, c’est rien pour une BDD ^^

+0 -0

Salut @Moté,

Merci pour ton aide. Effectivement j’ai fini par trouver, en faisant ça du côté des SideBySideSubscription. La requête est la suivante :

accepted_subscriptions = (
            self.get_accepted_subscriptions()
            .annotate(
                party_1_ct=models.Count(
                    "party_1", Q(party_1__round_number=round_number), distinct=True
                ),
                party_2_ct=models.Count(
                    "party_2", Q(party_2__round_number=round_number), distinct=True
                ),
            )
            .values("id", "team__name", "party_1_ct", "party_2_ct")
        )

Cela me renvoie la liste des SideBySideSubscription (acceptées, mais c’est juste un filtre sur le statut), annotées avec le nombre de relations party_1/party_2.

Et effectivement je fais d’habitude des prefetch, mais comme j’utilise values à la fin, il ne semble pas y en avoir besoin (d’après Django Debug Toolbar, qui ne me montre qu’une seule requête faite).

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte