2013-03-26 24 views
4

我想弄清楚一个棘手的Django查询,我希望你能帮上忙。我有这样的模式:如何返回表格中最受欢迎的物品,但每个物品的唯一位置?

class ActiveVenue(models.Model): 
    event = models.ForeignKey(Event) 
    venue = models.ForeignKey(Venue) 

class Venue(models.Model): 
    name = models.CharField(max_length=200) 

class Event(models.Model): 
    user = models.ForeignKey(User) 
    name = models.CharField(max_length=200) 

在我的应用程序有很多活动和每个事件可以有多个活动场地,因此我目前的数据结构。如果您的解决方案不是“将您的模型更改为foo”,而是已经部署的网站,我希望保留当前的模型结构。

我想编写一个查询,返回最受欢迎的场地,但每个用户只会计算一次场地。例如,如果我有一个用户有四个活动,并且他们每次都使用相同的场地,我只想在确定最受欢迎的场地时统计一次场地。

为了说明这一点,想象这是我的数据:

event: A user: bob venue: The Hill 
event: B user: bob venue: The Hill 
event: C user: bob venue: The Hill 
event: D user: jane venue: The Oaks 
event: E user: sarah venue: The Pound 
event: F user: david venue: The Pound 
event: G user: ron venue: The Oaks 
event: H user: erica venue: The Oaks 

这里流行的顺序将是:

1. The Oaks 
2. The Pound 
3. The Hill 

任何建议我如何编写一个查询来做到这一点,双方的Postgres工作和sqlite(换句话说,不依赖于distinct()(在sqlite中不支持))?

谢谢!

+0

我很确定'Disctinct'应该在sqlite中工作。按照https://docs.djangoproject.com/en/dev/ref/models/querysets/#distinct,你不能在sqlite中指定参数,但该函数应该可以工作。 – miki725 2013-03-26 05:33:47

+0

考虑到用户限制,我不认为你可以使用Django ORM来做到这一点。为此,您似乎必须编写一些手动SQL。 – miki725 2013-03-26 05:35:27

+0

@ miki725我同意,如果它被抽象为一个返回集合的函数,它可以在支持distinct()的情况下推迟到ORM,否则手动SQL。 – 2013-03-26 22:15:55

回答

1

这是行不通的吗?

from collections import Counter 
results = Counter([vid for vid, eid in ActiveVenue.objects.values_list("venue_id", "event_id").distinct()] 
+0

+1,collections.Counter质量非常高,专为此目的而设计。在获得了上面的代码之后,诸如'results.most_common(3)'之类的东西应该给你你正在寻找的答案,很好的排序和一切。 – robru 2013-03-26 06:21:18

+0

虽然这个工作,我认为我们应该避免在Python中迭代整个查询集。仅在切片(分页)后迭代。如果我们迭代原始查询集而不先切片,则查询集可能非常大,并且视图可能需要很长时间才能做出响应。 – 2013-03-26 06:32:17

0

看看是否有这样的工作

ActiveVenue.objects.all().annotate(score=Count('event__user', distinct=True)).order_by('-score') 
0

这是我的版本(抛光试验):

models.py

class Venue(models.Model): 
    name = models.CharField(max_length=200) 

    def __unicode__(self): 
     return self.name 

    def ranking(self): 
     count=0 
     actives = ActiveVenue.objects.filter(
      venue__name=self.name).values(
      'event__user__username', 'venue__name').distinct() 
     for active in actives: 
      count += 1 
     return count 

views.py

def getRanking(anObject): 
    return anObject.ranking() 

def myview(request): 
    venues = list(Venue.objects.filter()) 
    venues.sort(key=getRanking, reverse=True) 
    return render(request,'page.html',{'venues': venues}) 

模板

{% for venue in venues %} 
    {{forloop.counter}}. {{venue}}<br/> 
{% endfor %} 

输出:

  1. 奥克斯
  2. 英镑兑美元
  3. 希尔
+0

再次,这将所有内容加载到内存中,并将整个表格排序在内存中。不是一个好习惯。如果某个SQL查询无法实现,那么应该对该模式进行非规范化处理,使其成为可能,而不是在框架代码中加载所有内容并进行排序/过滤。 – 2013-03-26 08:01:21

+0

@OwaisLone这只会按照他的要求对活动场地进行排序,但我并没有把它放在过滤器中。 – catherine 2013-03-26 08:15:27

+0

我不是说它为乔诺工作。真的取决于他将拥有多少ActiveVenues。只是指出一个好的做法。 – 2013-03-26 08:25:15

0

这是我的问题。首先,这是一个查询,它将获取与相关用户相对应的Venue ID和分数。

testquery = ActiveVenue.objects.values("venue").annotate(score=Count("event__user", distinct=True)).order_by("-score") 

结果

[{'score': 3, 'venue': 2}, {'score': 2, 'venue': 3}, {'score': 1, 'venue': 1}] 

接下来,会场ID将被放置在一个新的列表。

query_ids = [item["venue"] for item in testquery] 
[2, 3, 1] 

接下来,必须获得相应的Venue对象。

tempresult = Venue.objects.filter(id__in=query_ids) 
[<Venue: The Hill>, <Venue: The Oaks>, <Venue: The Pound> 

最后一个列表解析会进行重新排序基于先前获得的分数场馆对象。

result = [venue for venue_id in query_ids for venue in tempresult if venue_id == venue.id] 
[<Venue: The Oaks>, <Venue: The Pound>, <Venue: The Hill>] 

这给出了基于测试数据的正确结果。

相关问题