删除Django ORM中的重复项 - 多行

时间:2012-12-04 09:57:57

标签: django django-models

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

丹尼尔罗斯曼对this question的回答似乎是恰当的,但我不确定如何将其扩展到每个对象有四个字段进行比较的情况。

谢谢,

W上。

1 个答案:

答案 0 :(得分:66)

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约束。

这会在DB中留下最大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()之前添加空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