4 つのフィールドを持つモデルがあります。データベースから重複したオブジェクトを削除するにはどうすればよいですか?
ダニエル・ローズマンの回答この質問適切と思われますが、オブジェクトごとに比較するフィールドが 4 つある状況にこれを拡張する方法がわかりません。
ありがとう、
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
これにより、DB 内で最大のレコードが残ります。元のレコード (最初のレコード) を保持したい場合は、 でコードを少し変更します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()
。 ;-)
取引
もちろん、すべてを 1 回のトランザクションで実行することを検討する必要があります。
https://docs.djangoproject.com/en/3.2/topics/db/transactions/#django.db.transaction.atomic