Группировка моделей по полевому сходству в Django / PostgreSQL - PullRequest
0 голосов
/ 16 января 2019

Допустим, у меня есть 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-стране?

...