2016-07-25 11 views
1

Ich versuche, ein Modell in Django 1.9 zu bauen, die ein Schlüssel, Wert-Paar (Wörterbuch) hat Ermöglicht die Aggregation von Abfragegruppen (min, mix, usw.). Ich habe versucht, die JSONField zu verwenden:Wie ein Dictionary-Feld in einem Django-Modell zu haben und Aggregation (min/max) auf diesem Feld über Dictionary Keys

#models.py 
from django.contrib.postgres import fields as pgfields 
class Entry(models.Model): 
    pass 

class Scorer(models.Model): 
    name = models.CharField(max_length=100) 

class EntryScoreSet(models.Model): 
    scorer = models.ForeignKey(Scorer) 
    entry = models.ForeignKey(Entry, related_name="scorecard") 
    scores = pgfields.JSONField(default={}) 
.... 
# shell test 
import random 
entry = Entry() 
scorer,_ = Scorer.objects.get_or_create(name="scorer1") 
entry.save() 
for i in range(0,10): 
    scores = dict(scoreA=random.random(), 
        scoreB=random.random(), 
        scoreC=random.random(), 
       ) 
    entry_score_set=EntryScoreSet(scores=scores, entry=entry, scorer=scorer) 
    entry_score_set.save() 

entry.scorecard.filter(scorer="scorer1").aggregate(Max("scores__scoreA")) 

Aber ich laufe in die Fehler von this ticket (im Grunde wird die Aggregation wird nicht unterstützt).

Eine zweite Option ist ein Schlüssel, Wert-Paar-Modell (ähnlich this answer) zu verwenden:

class Score(models.Model): 
    entry_score_set = models.ForeignKey(EntryScoreSet, db_index=True, 
            related_name="scores") 
    key = models.CharField(max_length=64, db_index=True) 
    value = models.FloatField(db_index=True) 

Aber ich weiß nicht, wie man eine Aggregation über eine Abfragesatz für einen bestimmten ein Schlüssel bekommen würde Wert.

Wie würde ich ein Schlüssel/Wert-Paar-Feld in Django implementieren, das die Aggregation einer Abfrage für den Wert eines bestimmten Schlüssels ermöglicht?

EDIT:

Hier ist ein Ausschnitt, das zeigt, was ich mit Pandas tun will und die zweite Option (Schlüssel, Paar-Modell):

import django_pandas.io as djpdio 
scds=Scorecard.objects.filter(
     entry__in=Entry.objects.order_by('?')[:10], 
     scorer__name="scorer1") 
scorecard_base=djpdio.read_frame(scds,fieldnames=["id","entry__id","scorer__name","scores__id"]) 
scores=djpdio.read_frame(Score.objects.filter(scorecard__in=scds),fieldnames=["id","key","value"]) 
scorecard_=(scorecard_base 
     .merge(scores,left_on="scores__id",right_on="id")   
     .pivot_table(index="entry__id",columns="key",values="value").reset_index()) 
scorecard=scorecard_base.merge(scorecard_,on="entry__id") 
scorecard["scoreA"].max() 

Ist so etwas möglich Djangos mit ORM? Wie wäre die Effizienz mit der Verwendung der Pandas Pivot-Funktion vergleichbar?

+1

Hat die 'EntryScoreSet' Laden etwas anderes, dass Noten:

entry.scorecard.filter(scorer=some_scorer).values('scores__key')\ .annotate(Max('scores__value')).order_by() 

Diese Sie eine Ausgabe wie so geben werden? Wenn nicht, könnten Sie einfach ein "Score" -Modell mit einem Fremdschlüssel direkt an "Entry" senden, und dann ist die Aggregation einfach (gerne ein Beispiel zu veröffentlichen, sobald Sie geklärt haben, ob das Zwischenmodell erforderlich ist). – solarissmoke

+0

Es hat einen zusätzlichen Fremdschlüssel, Einträge haben Punkte von mehreren Scorern. Ich werde aktualisieren. – jmerkow

+0

Wäre es möglich, die Schlüssel, Wert-Paare in zwei Arrays zu trennen und diese Operation durchzuführen? ein ArrayField (CharField), ArrayField (FloatField). Vielleicht verwenden Sie einen F() Ausdruck, um den Index von 'scoreA' zu erhalten – jmerkow

Antwort

1

Sie können dies mit conditional expressions, unter Verwendung der zweiten Modellstruktur, die Sie vorgeschlagen haben (Score mit einem Fremdschlüssel zu EntryScoreSet).

from django.db.models import Case, When, Max, FloatField 

entry.scorecard.all().annotate(
    max_score_key1=Max(
     Case(
      When(scores__key='key1', then='scores__value'), 
      default=0, 
      output_field=FloatField() 
     ) 
    ), 
    max_score_key2=Max(
     Case(
      When(scores__key='key2', then='scores__value'), 
      default=0, 
      output_field=FloatField() 
     ) 
    ) 
) 

Dies würde eine max_score_key1 Eigenschaft der resultierenden EntryScoreSet Objekte hinzufügen, die Ihnen den maximalen Wert für all Scores gibt, die ein key von key1 haben. Ähnlich max_score_key2 für Scores mit key2 usw.


bearbeiten: basierend auf Konversation in den Kommentaren sieht es aus wie Sie das Maximum für jede Taste in Score über die gesamte queryset erhalten möchten. Sie können das tun, etwa so:

[ 
    {'scores__key': 'key1', 'scores__value__max': 16.0}, 
    {'scores__key': 'key2', 'scores__value__max': 15.0}, 
    .... 
] 
+0

YHere, Standard sollte der minimal mögliche Wert sein? I.e. Für random.random() das ist Null, aber für andere Bereiche sollte es sein, was auch immer die untere Grenze ist? Außerdem nehme ich an, dass entry.scorecard.all ein beliebiges Abfrage-Set sein kann. – jmerkow

+0

Ja, genau - Sie können es auf etwas anderes einstellen, wenn das mit Ihren Daten sinnvoller ist. – solarissmoke

+0

Ok danke. Ich werde das ausprobieren und dann werde ich das akzeptieren. Wie schwer belastet das die DB? Das Abfrage-Set wird in der Regel über 300 Einträge mit 2-20 Bewertungen pro Eintrag haben. – jmerkow