Ответ 1
def remove_duplicated_records(model, fields):
"""
Removes records from 'model' duplicated on 'fields'
while leaving the most recent one (biggest 'id').
"""
duplicates = model.objects.values(*fields)
# override any model specific ordering (for '.annotate()')
duplicates = duplicates.order_by()
# group by same values of 'fields'; count how many rows are the same
duplicates = duplicates.annotate(
max_id=models.Max("id"), count_id=models.Count("id")
)
# leave out only the ones which are actually duplicated
duplicates = duplicates.filter(count_id__gt=1)
for duplicate in duplicates:
to_delete = model.objects.filter(**{x: duplicate[x] for x in fields})
# leave out the latest duplicated record
to_delete = to_delete.exclude(id=duplicate["max_id"])
to_delete.delete()
Ты не должен делать это часто. Вместо этого используйте ограничения unique_together
для базы данных.
Это оставляет запись с самым большим id
в БД. Если вы хотите сохранить исходную запись (первую), немного измените код с помощью models.Min
. Вы также можете использовать совершенно другое поле, например, дату создания или что-то в этом роде.
Базовый SQL
При аннотировании django ORM использует оператор GROUP BY
для всех полей модели, используемых в запросе. Таким образом, использование метода .values()
. GROUP BY
сгруппирует все записи с одинаковыми значениями. Дублированные (более одного id
для unique_fields
) позже отфильтровываются в операторе HAVING
, сгенерированном .filter()
для аннотированного QuerySet
.
SELECT
field_1,
…
field_n,
MAX(id) as max_id,
COUNT(id) as count_id
FROM
app_mymodel
GROUP BY
field_1,
…
field_n
HAVING
count_id > 1
Дублированные записи впоследствии удаляются в цикле for
, за исключением наиболее частого для каждой группы.
Пусто .order_by()
Просто чтобы быть уверенным, всегда целесообразно добавить пустой вызов .order_by()
перед агрегацией QuerySet
.
Поля, используемые для упорядочения QuerySet
, также включены в оператор GROUP BY
. Пустой .order_by()
переопределяет столбцы, объявленные в модели Meta
, и в результате они не включаются в запрос SQL (например, сортировка по дате может испортить результаты).
Вам может не потребоваться переопределить его в текущий момент, но кто-то может добавить порядок по умолчанию позже и, следовательно, испортить ваш драгоценный код удаления дубликатов, даже не зная об этом. Да, я уверен, что у вас 100% тестовое покрытие...
Просто добавьте пустой .order_by()
, чтобы быть в безопасности. ;-)
сделка
Конечно, вы должны рассмотреть возможность сделать все это в одной транзакции.
https://docs.djangoproject.com/en/1.11/topics/db/transactions/#django.db.transaction.atomic