30

4つのフィールドを持つモデルがあります。データベースから重複するオブジェクトを削除するにはどうすればよいですか?

この質問に対するDanielRosemanの答えは適切なようですが、オブジェクトごとに比較するフィールドが4つある状況にこれを拡張する方法がわかりません。

ありがとう、

W。

4

1 に答える 1

76
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これにより、DB で最大のレコードが残ります。元のレコード (最初のレコード) を保持したい場合は、 でコードを少し変更しますmodels.Min。作成日など、まったく異なるフィールドを使用することもできます。

基礎となる SQL

django ORM に注釈を付ける場合GROUP BY、クエリで使用されるすべてのモデル フィールドでステートメントを使用します。したがって、.values()メソッドの使用。GROUP BYこれらの値が同一であるすべてのレコードをグループ化します。id重複したもの (の複数) は、 on annotatedによって生成されたステートメント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()a を集約する前に空の呼び出しを追加するのが常に賢明QuerySetです。

注文に使用されるフィールドQuerySetGROUP BYステートメントに含まれています。.order_by()モデルで宣言された空のオーバーライド列はMeta、結果として SQL クエリに含まれません (たとえば、日付によるデフォルトの並べ替えは結果を台無しにする可能性があります)。

現時点ではオーバーライドする必要はないかもしれませんが、後で誰かがデフォルトの順序を追加し、それを知らずに貴重な重複削除コードを台無しにする可能性があります。はい、100% のテスト カバレッジがあると思います…</p>

.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

于 2012-12-04T10:19:31.707 に答える