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

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

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

谢谢,

W.

【问题讨论】:

    标签: django django-models


    【解决方案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()。出于某种原因,它不想从过滤器语句中的重复变量中解压缩它:/
    猜你喜欢
    • 2021-01-15
    • 2011-08-18
    • 1970-01-01
    • 2022-01-05
    • 2018-04-07
    • 2020-02-13
    • 2021-12-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多