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

问题描述 投票:0回答:2

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

丹尼尔·罗斯曼对这个问题的回答似乎很合适,但我不确定如何将其扩展到每个对象有四个字段需要比较的情况。

谢谢,

W.

django django-models
2个回答
78
投票
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/aggregation/#interaction-with-default-ordering-or-order-by

交易

当然,您应该考虑在一次交易中完成这一切。

https://docs.djangoproject.com/en/3.2/topics/db/transactions/#django.db.transaction.atomic


1
投票

如果您想删除单列或多列上的重复项,则无需迭代数百万条记录。

  1. 获取所有唯一列(不要忘记包含主键列)

    fetch = Model.objects.all().values("id", "skuid", "review", "date_time")
    
  2. 使用 pandas 读取结果(我使用 pandas 代替 ORM 查询)

    import pandas as pd
    df = pd.DataFrame.from_dict(fetch)
    
  3. 在唯一列上删除重复项

    uniq_df = df.drop_duplicates(subset=["skuid", "review", "date_time"])
    ## Dont add primary key in subset you dumb
    
  4. 现在,您将获得唯一的记录,您可以从中选择主键

    primary_keys = uniq_df["id"].tolist()
    
  5. 最后,是展示时间(从记录中排除那些id并删除其余数据)

    records = Model.objects.all().exclude(pk__in=primary_keys).delete()
    
© www.soinside.com 2019 - 2024. All rights reserved.