Допустим, у меня есть Person
модель:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=256)
Экземпляры этой модели были заполнены из существующего источника данных, и теперь я хотел бы объединить похожие People
вместе в name
.
Тривиально найти имена, похожие на данное имя («Боб», здесь):
Person.objects.all()
# Annotate each item with its unaccented similarity ranking with the given name
.annotate(similarity=TrigramSimilarity("name__unaccent", "Bob"))
# And filter out anything below the given threshold
.filter(similarity__gt=0.9)
Это создаст (что-то близко) следующее:
SELECT "person"."id",
"person"."name",
SIMILARITY("cases_person"."name", 'Bob') AS "similarity"
FROM "cases_person"
WHERE SIMILARITY("cases_person"."name", 'Bob') > 0.9
Но я хочу группировок похожих людей. Это может быть сделано в Python с некоторыми изменениями выше:
from django.contrib.postgres.search import TrigramSimilarity
from .models import Person
people = Person.objects.all()
threshold = 0.9
people_ids_to_merge = []
processed = set()
for name in people.values_list("name", flat=True):
similar_people = (
# We must exclude any people that have already been processed, because
# this means they are already in people_ids_to_merge. If we didn't
# exclude them here, we would get duplicates in people_ids_to_merge
people.exclude(id__in=processed)
# Annotate each item with its unaccented similarity ranking with the current name
.annotate(similarity=TrigramSimilarity("name__unaccent", name))
# And filter out anything below the given threshold
.filter(similarity__gt=threshold)
)
num_similar_people = similar_people.count()
if num_similar_people > 1:
print(f"Found {num_similar_people} names similar to {name!r}")
ids = list(similar_people.values_list("id", flat=True))
people_ids_to_merge.append(ids)
processed.update(ids)
print("Groups of IDs of similar people:")
print(people_ids_to_merge)
Пример вывода:
Groups of IDs of similar people:
[[3, 8], [9, 17, 21]]
Однако это, очевидно, приводит к одному запросу для каждой группировки. Есть ли способ сделать это изначально в PostgreSQL? Или более оптимальный способ подойти к этому в Python-стране?