查询所有行并返回每个重复的最新行

时间:2010-10-24 15:19:34

标签: django model filter unique

嘿伙计们,我有一个不具有唯一身份的模特。每个型号也有一个日期。我想返回所有结果,但只返回共享ID的每一行的最新结果。该模型看起来像这样:

class MyModel(models.Model):
    my_id = models.PositiveIntegerField()
    date  = models.DateTimeField()
    title = models.CharField(max_length=36)


## Add some entries
m1 = MyModel(my_id=1, date=yesterday, title='stop')
m1.save()

m2 = MyModel(my_id=1, date=today, title='go')
m2.save()

m3 = MyModel(my_id=2, date=today, title='hello')
m3.save()

现在尝试检索这些结果:

MyModel.objects.all()... # then limit duplicate my_id's by most recent

结果应仅 m2 m3

3 个答案:

答案 0 :(得分:6)

您将无法仅使用ORM执行此操作,您需要获取所有记录,然后在Python中丢弃重复项。

例如:

objs = MyModel.objects.all().order_by("-date")
seen = set()
keep = []
for o in objs:
    if o.id not in seen:
        keep.append(o)
        seen.add(o.id)

这是一些可以从数据库中获得所需内容的自定义SQL:

select * from mymodel where (id, date) in (select id, max(date) from mymodel group by id)

您应该能够将其调整为在ORM中使用。

答案 1 :(得分:0)

您还应该考虑将上述逻辑抽象为经理:

http://docs.djangoproject.com/en/dev/topics/db/managers/

这样你可以调用类似MyModel.objects.no_dupes()的东西,在那里你可以在一个管理器中定义no_dupes()并在那里布置逻辑Ned。

您的models.py现在看起来像这样:

class MyModelManager(models.Manager):
    def no_dupes:
        objs = MyModel.objects.all().order_by("-date")
        seen = set()
        keep = []
        for o in objs:
            if o.id not in seen:
                keep.append(o)
                seen.add(o.id)
        return keep

class MyModel(models.Model):
    my_id = models.PositiveIntegerField()
    date  = models.DateTimeField()
    title = models.CharField(max_length=36)
    objects = MyModelManager()

使用上面的代码,你可以调用:MyModel.objects.no_dupes(),这应该给你想要的结果。看起来你甚至可以覆盖all()函数,如果你想要的话:

http://docs.djangoproject.com/en/1.2/topics/db/managers/#modifying-initial-manager-querysets

我发现经理是一个更好的解决方案,以防你需要在整个项目的多个视图中使用它,这样你就不必重复代码X次了。

答案 2 :(得分:-1)

正如Ned所说,我不知道如何用ORM做到这一点。但是您可以使用db来限制在python中for循环中必须完成的工作量。

这个想法是使用Django的annotate(基本上运行group_by)来查找具有多个具有相同my_id的行的所有实例,并将其处理为Ned建议。然后对于余数(没有重复),你可以抓住各行。

from django.db.models import Count, Q
annotated_qs = MyModel.objects.annotate(num_my_ids=Count('my_id')).order_by('-date')
dupes = annotated_qs.filter(num_my_ids__gt=1)
uniques = annotated_qs.filter(num_my_ids__lte=1)
for dupe in dupes:
   ... # just keep the most recent, as Ned describes
keep_ids = [keep.id for keep in keeps]
latests = MyModel.objects.filter(Q(id__in=keep_ids) | Q(id__in=uniques))

如果你只有少量的欺骗,这将意味着你的for循环要短得多,代价是额外的查询(以获得欺骗)。