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

Posted

技术标签:

【中文标题】删除 Django ORM 中的重复项——多行【英文标题】:Remove duplicates in Django ORM -- multiple rows 【发布时间】:2012-12-04 09:57:56 【问题描述】:

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

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

谢谢,

W.

【问题讨论】:

【参考方案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
        # 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 ORM 在查询中使用的所有模型字段上使用GROUP BY 语句时。因此使用.values()方法。 GROUP BY 将对具有相同值的所有记录进行分组。重复的(unique_fields 不止一个id)稍后在.filter() 对注释QuerySet 生成的HAVING 语句中过滤掉。

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 之前添加一个空的 .order_by() 调用总是明智的。

用于排序QuerySet 的字段也包含在GROUP BY 语句中。空的 .order_by() 会覆盖模型的 Meta 中声明的列,因此它们不包含在 SQL 查询中(例如,默认的按日期排序可能会破坏结果)。

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

只需添加空的.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

【讨论】:

谢谢!但是,为了让我能理解(我仍然是一个 Django 新手),你能解释一下每一步发生了什么吗?我知道MyModel.objects.values(*unique_fields) 会生成一组字典,每个字典都与一个对象有关。但后来我迷路了——注释在做什么? 太棒了!完美运行!我花了相当多的研究和思考才能确切地了解 如何 它是如何工作的(你的解释帮助了我很多,并帮助我弄清楚了我必须阅读的内容......)但它确实有效!再次感谢(并为延迟回复此问题表示歉意) 为什么要排除最大值?我会注释 Min 并排除 min,因为它们是原始的。最后它仍然会删除 dups,只保留最小的 id 而不是较高的。 @AndreMiras 取决于用例。有时最后的信息有最新的信息。 我在尝试运行上面的代码时遇到了异常:NameError: name 'duplicate' is not defined (Python3.4, Django1.11)。这对我有用:Role.objects.filter(**field_1: d[field_1], ..., field_n: d[field_n]).exclude(id=d['max_id']).delete()。出于某种原因,它不想从过滤器语句中的重复变量中解压缩它:/

以上是关于删除 Django ORM 中的重复项——多行的主要内容,如果未能解决你的问题,请参考以下文章

如何在从多个表中获取多行的同时删除 sql JOIN 中的重复项

如何从 Javascript 中的数组中删除重复项?

Django-仅在存在字段时删除重复项

MYSQL - 将具有多个重复值的行组合起来,然后删除重复项

Django url在多行模板中的参数[重复]

django中的orm中怎么对筛选结果去重