删除 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 中的重复项