30

我有一个有四个字段的模型。如何从我的数据库中删除重复的对象?

Daniel Roseman 对这个问题的回答似乎很合适,但我不确定如何将其扩展到每个对象有四个字段要比较的情况。

谢谢,

W。

4

1 回答 1

76
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
        # you can use `Min` if you wish to leave out the first record
        to_delete = to_delete.exclude(id=duplicate["max_id"])

        to_delete.delete()

你不应该经常这样做。改为对数据库使用unique_together约束。

这留下了id数据库中最大的记录。如果要保留原始记录(第一个),请使用models.Min. 您还可以使用完全不同的字段,例如创建日期或其他内容。

底层 SQL

注释 django ORMGROUP BY在查询中使用的所有模型字段上使用语句时。从而使用.values()方法。GROUP BY将对具有相同值的所有记录进行分组。重复的(不止一个idfor )稍后在on annotated生成的语句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语句中。Empty.order_by()会覆盖模型Meta中声明的列,因此它们不包含在 SQL 查询中(例如,按日期的默认排序可能会破坏结果)。

您目前可能不需要覆盖它,但有人可能会在稍后添加默认排序,从而破坏您宝贵的删除重复代码,甚至不知道这一点。是的,我确信你有 100% 的测试覆盖率……</p>

只需添加空.order_by()以确保安全。;-)

https://docs.djangoproject.com/en/3.2/topics/db/aggregation/#interaction-with-default-ordering-or-order-by

交易

当然,您应该考虑在单个事务中完成所有操作。

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

于 2012-12-04T10:19:31.707 回答