2009-07-21 6 views
1

J'ai joué avec la nouvelle fonctionnalité d'agrégation dans l'ORM de Django, et il y a une classe de problèmes que je pense pense que devrait être possible, mais je n'arrive pas à obtenir pour travailler. Le type de requête que j'essaie de générer est décrit here.Django - trouver le membre extrême de chaque groupe

Alors, disons que je les modèles suivants -

class ContactGroup(models.Model): 
    .... whatever .... 

class Contact(models.Model): 
    group = models.ForeignKey(ContactGroup) 
    name = models.CharField(max_length=20) 
    email = models.EmailField() 
... 

class Record(models.Model): 
    contact = models.ForeignKey(Contact) 
    group = models.ForeignKey(ContactGroup) 
    record_date = models.DateTimeField(default=datetime.datetime.now) 

    ... name, email, and other fields that are in Contact ... 

Ainsi, chaque fois qu'un contact est créé ou modifié, un nouvel enregistrement est créé, qui enregistre les informations tel qu'il apparaît dans le contact à ce moment-là , avec un horodatage. Maintenant, je veux une requête qui, par exemple, renvoie l'instance d'enregistrement la plus récente pour chaque contact associé à un groupe de contact. En pseudo-code:

group = ContactGroup.objects.get(...) 
records_i_want = group.record_set.most_recent_record_for_every_contact() 

Une fois que je reçois ce compris, je veux juste être en mesure de jeter un filter(record_date__lt=some_date) sur le queryset, et obtenir l'information telle qu'elle existait à some_date.

Quelqu'un a des idées?

edit: Il semble que je ne suis pas vraiment clair. En utilisant des modèles comme ceux-ci, je veux un moyen de faire ce qui suit à ORM pur django (sans supplément()):

ContactGroup.record_set.extra(where=["history_date = (select max(history_date) from app_record r where r.id=app_record.id and r.history_date <= '2009-07-18')"]) 

Mettre la sous-requête dans la clause where est une seule stratégie pour résoudre ce problème, les autres sont assez bien couvert par le premier lien que j'ai donné ci-dessus. Je sais que les sous-sélections where-clause ne sont pas possibles sans l'utilisation de extra(), mais je pensais que l'une des autres méthodes était peut-être rendue possible par les nouvelles fonctionnalités d'agrégation.

+0

Pourquoi avez-vous une clé étrangère à un groupe de contact à partir des modèles d'enregistrement et de contact? En outre, un nitpick mineur, mais en utilisant auto_now ou auto_now_add sur votre DateTimeField au lieu de spécifier explicitement la valeur par défaut est généralement plus pratique. – oggy

+0

Il existe une clé étrangère provenant de Record car il s'agit d'une copie exacte de Contact, plus la clé étrangère à contacter et le record_date. C'est un aperçu d'un contact à un moment donné. En outre, auto \ _now et auto \ _now \ _add sont connus pour causer des bogues difficiles à diagnostiquer, sont détestés par les committers de base, et ne sont guère plus pratiques que datetime.datetime.now –

Répondre

0

Il semble que vous souhaitiez conserver des enregistrements des modifications apportées aux objets dans Django.

Pro Django a une section dans le chapitre 11 (Amélioration des applications) dans lequel l'auteur montre comment créer un modèle qui utilise un autre modèle comme client qu'il suit pour les insertions/suppressions/mises à jour. Le modèle est généré dynamiquement à partir du client définition et s'appuie sur des signaux. Le code affiche la fonction most_recent() mais vous pouvez l'adapter pour obtenir l'état de l'objet à une date donnée.

Je suppose que c'est le suivi dans Django qui est problématique, pas le SQL pour l'obtenir, non?

+0

Actuellement, j'utilise les HistoricalRecords app de Pro Django pour suivre l'histoire - je ne l'ai pas mentionné juste pour le garder simple. Cette application vous permet de faire ce que je demande à propos d'instances d'objets uniques, mais pas pour des ensembles d'objets, comme j'essaye de le faire. –

0

D'abord, je vous signale que:

ContactGroup.record_set.extra(where=["history_date = (select max(history_date) from app_record r where r.id=app_record.id and r.history_date <= '2009-07-18')"]) 

ne vous obtiendrez le même effet que:

records_i_want = group.record_set.most_recent_record_for_every_contact() 

La première requête retourne tous les enregistrements associés à un groupe particulier (ou associé à l'un des contacts d'un groupe particulier) qui a un record_date inférieur à la date/heure spécifiée dans le supplément. Exécuter sur la coquille et faites ceci pour examiner la requête django créé:

from django.db import connection 
connection.queries[-1] 

qui révèle:

'SELECT "contacts_record"."id", "contacts_record"."contact_id", "contacts_record"."group_id", "contacts_record"."record_date", "contacts_record"."name", "contacts_record"."email" FROM "contacts_record" WHERE "contacts_record"."group_id" = 1 AND record_date = (select max(record_date) from contacts_record r where r.id=contacts_record.id and r.record_date <= \'2009-07-18\') 

Pas exactement ce que vous voulez, non?

Maintenant, la fonction d'agrégation est utilisée pour extraire des données agrégées et non des objets associés à des données agrégées. Donc, si vous essayez de minimiser le nombre de requêtes exécutées en utilisant l'agrégation lorsque vous essayez d'obtenir group.record_set.most_recent_record_for_every_contact(), vous ne réussirez pas.

Sans utiliser l'agrégation, vous pouvez obtenir le dernier enregistrement pour tous les contacts associés à un groupe à l'aide:

[x.record_set.all().order_by('-record_date')[0] for x in group.contact_set.all()] 

En utilisant l'agrégation, le plus proche que je pouvais arriver à c'était:

group.record_set.values('contact').annotate(latest_date=Max('record_date')) 

Ce dernier renvoie une liste de dictionnaires tels que:

[{'contact': 1, 'latest_date': somedate }, {'contact': 2, 'latest_date': somedate }] 

pour chaque contact dans un groupe donné et la dernière date d'enregistrement associée. Quoi qu'il en soit, le nombre minimum de requêtes est probablement 1 + # de contacts dans un groupe. Si vous êtes intéressé à obtenir le résultat en utilisant une seule requête, c'est également possible, mais vous devrez construire vos modèles différemment. Mais c'est un aspect totalement différent de votre problème. J'espère que cela vous aidera à comprendre comment aborder le problème en utilisant l'agrégation/les fonctions ORM normales.

Questions connexes