Запросить все строки и вернуть самый последний из каждого дубликата - PullRequest
3 голосов
/ 24 октября 2010

Эй, ребята, у меня есть модель с уникальным идентификатором.У каждой модели также есть своя дата.Я хотел бы вернуть все результаты, но только самые последние из каждой строки, которая имеет идентификаторы.Модель выглядит примерно так:

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

Результаты должны быть только м2 и м3

Ответы [ 3 ]

6 голосов
/ 24 октября 2010

Вы не сможете сделать это только с помощью 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 голос
/ 24 октября 2010

Вы также должны изучить абстрагирование логики выше в менеджере:

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

Таким образом, вы можете вызвать что-то вроде MyModel.objects.no_dupes (), где вы будете определять no_dupes () в менеджере и выполнять логику Неда, изложенную там.

Ваш 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 несколько раз.

0 голосов
/ 05 декабря 2011

Как говорит Нед, я не знаю, как это сделать с помощью ORM. Но вы можете использовать БД для ограничения объема работы, которую вы должны выполнять в цикле for в python.

Идея состоит в том, чтобы использовать annotate Джанго (который в основном работает group_by), чтобы найти все экземпляры, которые имеют более одной строки с одинаковым my_id, и обработать их, как предполагает Нед. Затем для остатка (у которого нет дубликатов), вы можете просто взять отдельные строки.

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 будет намного короче за счет дополнительного запроса (чтобы получить дубликаты).

...