PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Comment enregistrer les classements quotidiens d'un modèle dans Django ?

Je suggérerais quelque chose de similaire à ce que e4c5 suggère , mais je voudrais aussi :

  • Générez un index sur la date des classements afin d'optimiser l'obtention de tous les classements sur une seule journée.

  • Marquez la date et l'élève comme unique_together . Cela évite la possibilité d'enregistrer deux rangs pour le même élève à la même date.

Les modèles ressembleraient à ceci :

from django.db import models

class Grade(models.Model):
    pass  # Whatever you need here...

class Student(models.Model):
    name = models.CharField(max_length=20)
    grade = models.ForeignKey(Grade)

class Rank(models.Model):

    class Meta(object):
        unique_together = (("date", "student"), )

    date = models.DateField(db_index=True)
    student = models.ForeignKey(Student)
    value = models.IntegerField()

Dans une application à part entière, je m'attendrais également à avoir des contraintes d'unicité sur Grade et Student mais le problème présenté dans la question ne fournit pas suffisamment de détails sur ces modèles.

Vous pourriez alors exécuter une tâche tous les jours avec cron ou quel que soit le gestionnaire de tâches que vous souhaitez utiliser (le céleri est également une option), pour exécuter une commande comme la suivante qui mettrait à jour les classements en fonction de certains calculs et purgerait les anciens enregistrements. Le code suivant est une illustration de la façon dont cela peut être fait. Le vrai code doit être conçu pour être généralement idempotent (le code suivant n'est pas parce que le calcul du rang est aléatoire) de sorte que si le serveur est redémarré au milieu d'une mise à jour, la commande peut simplement être réexécutée. Voici le code :

import random
import datetime
from optparse import make_option
from django.utils.timezone import utc

from django.core.management.base import BaseCommand
from school.models import Rank, Student

def utcnow():
    return datetime.datetime.utcnow().replace(tzinfo=utc)

class Command(BaseCommand):
    help = "Compute ranks and cull the old ones"
    option_list = BaseCommand.option_list + (
        make_option('--fake-now',
                    default=None,
                    help='Fake the now value to X days ago.'),
    )

    def handle(self, *args, **options):
        now = utcnow()
        fake_now = options["fake_now"]
        if fake_now is not None:
            now -= datetime.timedelta(days=int(fake_now))
            print "Setting now to: ", now

        for student in Student.objects.all():
            # This simulates a rank computation for the purpose of
            # illustration.
            rank_value = random.randint(1, 1000)
            try:
                rank = Rank.objects.get(student=student, date=now)
            except Rank.DoesNotExist:
                rank = Rank(
                    student=student, date=now)
            rank.value = rank_value
            rank.save()

        # Delete all ranks older than 180 days.
        Rank.objects.filter(
            date__lt=now - datetime.timedelta(days=180)).delete()

Pourquoi pas des cornichons ?

Plusieurs raisons :

  1. C'est une optimisation prématurée, et dans l'ensemble probablement pas une optimisation du tout. Certains les opérations peuvent être plus rapides, mais d'autres opérations sera plus lent. Si les classements sont décalés dans un champ sur Student ensuite, charger un élève spécifique en mémoire signifie charger toutes les informations de classement en mémoire avec cet élève. Cela peut être atténué en utilisant .values() ou .values_list() mais alors vous n'obtenez plus Student instances de la base de données. Pourquoi avoir Student instances en premier lieu et pas seulement accéder à la base de données brute ?

  2. Si je change les champs dans Rank , les fonctionnalités de migration de Django permettent facilement d'effectuer les modifications nécessaires lorsque je déploie la nouvelle version de mon application. Si les informations de classement sont conservées dans un champ, je dois gérer tout changement de structure en écrivant un code personnalisé.

  3. Le logiciel de base de données ne peut pas accéder aux valeurs dans un cornichon et vous devez donc écrire un code personnalisé pour y accéder. Avec le modèle ci-dessus, si vous souhaitez répertorier les étudiants par rang aujourd'hui (et que les rangs d'aujourd'hui ont déjà été calculés), vous pouvez :

    for r in Rank.objects.filter(date=utcnow()).order_by("value")\
        .prefetch_related():
        print r.student.name
    

    Si vous utilisez des cornichons, vous devez scanner tous les Students et détachez les rangs pour extraire celui du jour que vous voulez, puis utilisez une structure de données Python pour classer les étudiants par rang. Une fois cela fait, vous devez parcourir cette structure pour obtenir les noms dans l'ordre.