Как удалить все, кроме самого последнего объекта в каждой группе в Django

Я хочу сгруппировать объекты моей модели по трем полям, и удалить все объекты, кроме самых молодых для каждой группы .

Моя модель:

class DataFile(models.Model):
    filename = models.CharField(unique=True, max_length=256)
    timestamp = models.DateTimeField()
    tile_label = models.CharField(max_length=256, blank=True)
    region = models.CharField(max_length=256, blank=True)
    resolution = models.CharField(max_length=256, blank=True)

Этот запрос дает мне список значений, которые я хочу сохранить:

DataFile.objects.values('resolution', 'region', 'tile_label').annotate(maxfield=Max('timestamp'))

Результатом этого является

{'resolution': '30', 'region': 'SRC', 'tile_label': '10r_20d', 'maxfield': datetime.datetime(2021, 9, 11, 7, 13, tzinfo=<UTC>)}
{'resolution': '30', 'region': 'NRC', 'tile_label': '10r_20d', 'maxfield': datetime.datetime(2021, 8, 16, 2, 8, tzinfo=<UTC>)}
{'resolution': '30', 'region': 'NRC', 'tile_label': '100r_200d', 'maxfield': datetime.datetime(2021, 11, 15, 23, 5, tzinfo=<UTC>)}
{'resolution': '300', 'region': 'SRC', 'tile_label': '10r_20d', 'maxfield': datetime.datetime(2021, 11, 1, 13, 46, tzinfo=<UTC>)}
{'resolution': '300', 'region': 'NRC', 'tile_label': '10r_20d', 'maxfield': datetime.datetime(2021, 11, 5, 12, 20, tzinfo=<UTC>)}
{'resolution': '300', 'region': 'NRC', 'tile_label': '100r_200d', 'maxfield': datetime.datetime(2021, 11, 18, 5, 54, tzinfo=<UTC>)}
{'resolution': '30', 'region': 'SRC', 'tile_label': '100r_200d', 'maxfield': datetime.datetime(2021, 11, 5, 21, 8, tzinfo=<UTC>)}
{'resolution': '300', 'region': 'SRC', 'tile_label': '100r_200d', 'maxfield': datetime.datetime(2021, 11, 18, 8, 29, tzinfo=<UTC>)}

Теперь мне нужно отфильтровать все объекты DataFile, кроме объекта с наибольшей датой в каждой группе resolution, region, tile_label, а затем удалить их (т.е. оставить объект с наибольшей датой).

Как мне это сделать? Мне нужно сделать GROUP BY с values(), чтобы получить максимум из каждой группы, но это означает, что я больше не оперирую всем набором запросов.
Я думаю, что есть решение, связанное с подзапросами, но я так и не смог понять, как они работают.

Мне нужно решение на основе базы данных, поэтому зацикливание - не вариант. Я бы также предпочел избегать запросов __in по соображениям производительности (но в конечном итоге сделаю это, если не будет другого решения).

Этот запрос дает мне список значений, которые я хочу сохранить:

DataFile.objects.values('resolution', 'region', 'tile_label').annotate(maxfield=Max('timestamp'))

Этот запрос дает вам максимальную временную метку, но не говорит, к какой строке она относится. Вы можете получить это вторым запросом, но это кажется мне сложным.

Я попытался подумать, каким свойством должен обладать файл DataFile, чтобы претендовать на удаление. Ответ я нашел в том, что должен существовать более новый DataFile с таким же resolution/region/tile_label. Когда я перевожу это в Django, то получаю что-то вроде этого:

newer = DataFile.objects.filter(
     resolution=OuterRef('resolution'),
     region=OuterRef('region'),
     tile_label=OuterRef('tile_label'),
     timestamp__gt=OuterRef('timestamp'),
)

DataFile.objects.filter(Exists(newer)).delete()

Exists создает подзапрос, а OuterRef может использоваться для ссылки на поля из внешнего запроса.

https://docs.djangoproject.com/en/4.1/ref/models/expressions/#filtering-on-a-subquery-or-exists-expressions

В SQL это будет выглядеть примерно так (не проверено):

DELETE from datafiles WHERE EXISTS (
     SELECT id from datafiles as datafiles2 WHERE (
         datafiles2.resolution = datafiles.resolution
         AND datafiles2.region = datafiles.region
         AND datafiles2.tile_label = datafiles.tile_label
         AND datafiles2.timestamp >= datafiles.timestamp
     )
)
Вернуться на верх