【问题标题】:How to have a dictionary field in a django model and use aggregation (min/max) on that field across dictionary keys如何在 django 模型中拥有一个字典字段并跨字典键在该字段上使用聚合(最小值/最大值)
【发布时间】:2016-11-28 07:58:13
【问题描述】:

我正在尝试在 django 1.9 中构建一个模型,该模型具有一个键、值对(字典)字段,该字段还允许查询集聚合(min、mix 等)。 我尝试使用 JSONField:

#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"))

但是我遇到了来自this ticket 的错误(基本上不支持聚合)。

第二种选择是使用键值对模型(类似于this answer):

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)

但我不知道如何在查询集中获取特定键值的聚合。

如何在 Django 中实现一个键值对字段,以允许对特定键值的查询集进行聚合?

编辑:

这是一个 sn-p,它演示了我想使用 pandas 和第二个选项(键、对模型)做什么:

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()

使用 django 的 ORM 可以实现这样的事情吗?与使用 pandas pivot 函数相比,效率如何?

【问题讨论】:

  • EntryScoreSet 是否存储任何其他分数?如果没有,你可以有一个 Score 模型,其外键直接指向 Entry,然后聚合很简单(一旦你澄清是否需要中间模型,很高兴发布一个示例)。
  • 它有一个额外的外键,条目有多个评分者的分数。我会更新的。
  • 是否可以将键、值对分成两个数组并执行此操作?即 ArrayField(CharField)、ArrayField(FloatField)。也许使用 F() 表达式来获取 'scoreA' 的索引

标签: python django orm


【解决方案1】:

您可以通过 conditional expressions 执行此操作,使用您建议的第二个模型结构(ScoreEntryScoreSet 的外键)。

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()
        )
    )
)

这会将max_score_key1 属性添加到生成的EntryScoreSet 对象中,从而为具有keykey1 的所有Scores 提供最大值。同样max_score_key2Scoreskey2 等。


编辑:根据 cmets 中的对话,您似乎希望在整个查询集中获得 Score 中每个键的最大值。你可以这样做:

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

这会给你这样的输出:

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

【讨论】:

  • Y这里,默认应该是可能的最小值吗? IE。对于 random.random() 为零,但对于其他范围,它应该是下限是什么?另外,我假设 entry.scorecard.all 可以是任何查询集?
  • 是的,完全正确 - 如果这对您的数据更有意义,您可以将其设置为其他值。
  • 好的,谢谢。我要试试这个,然后我会接受这个。这给数据库带来了多大的负担?查询集通常有大约 300 个条目,每个条目有 2-20 个分数。
  • 不能给出一个定性的答案——你可能需要测试看看。一般来说,这将比在 Python 中执行相同的逻辑更高效、更快。
  • 所以这实际上没有用,但它帮助我弄清楚如何让它工作。为了使它工作,添加 .aggregation(max_score_key1=Max("key1"),max_score_key2=Max("key2")) (我更改了注释中的名称,我可以从注释函数中删除 Max,键是唯一的在我特定的 QueryString 中)。目标是整个查询集的最大值。如果你更新,我会接受你的回答。
猜你喜欢
  • 2015-09-25
  • 2014-02-22
  • 2017-01-04
  • 1970-01-01
  • 2016-05-19
  • 2015-04-22
  • 1970-01-01
  • 1970-01-01
  • 2017-08-26
相关资源
最近更新 更多