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()
UniqueContraint
。
这将在数据库中留下最大
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()
可以肯定的是,在聚合
.order_by()
之前添加一个空的 QuerySet
调用始终是明智的。
用于订购
QuerySet
的字段也包含在 GROUP BY
语句中。空 .order_by()
会覆盖模型的 Meta
中声明的列,结果它们不包含在 SQL 查询中(例如,按日期默认排序可能会破坏结果)。
您当前可能不需要覆盖它,但有人可能会在稍后添加默认排序,从而破坏您宝贵的删除重复代码,甚至不知道这一点。是的,我确信您有 100% 的测试覆盖率……
只需添加空
.order_by()
即可安全。 ;-)
交易
当然,您应该考虑在一次交易中完成这一切。
https://docs.djangoproject.com/en/3.2/topics/db/transactions/#django.db.transaction.atomic
获取所有唯一列(不要忘记包含主键列)
fetch = Model.objects.all().values("id", "skuid", "review", "date_time")
使用 pandas 读取结果(我使用 pandas 代替 ORM 查询)
import pandas as pd
df = pd.DataFrame.from_dict(fetch)
在唯一列上删除重复项
uniq_df = df.drop_duplicates(subset=["skuid", "review", "date_time"])
## Dont add primary key in subset you dumb
现在,您将获得唯一的记录,您可以从中选择主键
primary_keys = uniq_df["id"].tolist()
最后,是展示时间(从记录中排除那些id并删除其余数据)
records = Model.objects.all().exclude(pk__in=primary_keys).delete()