Подтвердить что ты не робот

Удалить дубликаты в Django ORM - несколько строк

У меня есть модель с четырьмя полями. Как удалить повторяющиеся объекты из моей базы данных?

Даниэль Роземан отвечает на этот вопрос, кажется уместным, но я не уверен, как это расширить до ситуации, когда для каждого объекта есть четыре поля.

Спасибо,

Ш.

4b9b3361

Ответ 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/aggregation/#interaction-with-default-ordering-or-order-by

сделка

Конечно, вы должны рассмотреть возможность сделать все это в одной транзакции.

https://docs.djangoproject.com/en/1.11/topics/db/transactions/#django.db.transaction.atomic