Как получить список всех отличительных первых букв заданного поля
Допустим, у меня есть класс Person, который состоит из поля last_name string.
Я хочу отобразить ссылки на все первые буквы имен, существующих в базе данных.
Например:
A B D E... когда есть Адамс, Браун, Дуглас, Эванс и нет никого, чья фамилия начинается с C.
Конечно, представление здесь не является проблемой, так как я хочу подготовить все это на бэкенде. Поэтому вопрос в том, как написать хорошую функцию модели или представления, которая обеспечит это.
Я бы хотел, чтобы он был независим от БД, однако, трюки для любой конкретной БД были бы бонусом.
Кроме того, я бы хотел, чтобы это было достаточно хорошо оптимизировано с точки зрения скорости, потому что это может быть много имен. Он должен быть менее наивным, чем этот самый простой алгоритм:
- Get all people
- Create set (because of the uniqueness of elements) of the first letters
- Sort and return
Так, например (в файле views.py):
names = Person.objects.values_list('last_name', flat=True)
letters = {name[0] for name in names}
letters_sorted = sorted(letters)
Конечно, я могу order_by первым или присвоить новый атрибут каждому объекту (содержащий первую букву), но я не думаю, что это ускорит процесс.
Я также думаю, что предположение о том, что все буквы используются является плохим предположением, если я буду проверять каждую букву, существует ли хотя бы одно имя для этой буквы ;-)
Какой подход будет наиболее эффективным для баз данных и django здесь?
Я бы сначала отсортировал имена, затем распаковал имена как аргументы для zip, затем использовал бы только первый кортеж, который выдает zip:
names = sorted(Person.objects.values_list('last_name', flat=True))
first_letters = next(zip(*names))
Это не использует наборы, не удаляет дубликаты или что-то подобное. Это критично? Если да, то можно сделать так:
names = Person.objects.values_list('last_name', flat=True)
first_letters = sorted(set(next(zip(*names))))
Хотя это вряд ли будет более производительным, чем то, что вы уже написали.
import string
alphabet_list = list(string.ascii_lowercase) + list(string.ascii_uppercase)
result = dict(map(lambda x: (x, Person.objects.filter(name__start_with=x).exists()), alphabet_list))
Вы также можете использовать методы orm для генерации списка инициалов (включая count):
from django.db.models import Count
from django.db.models.functions import Left
initials = (Person
.objects
.annotate(initial=Left('last_name', 1))
.values('initial')
.annotate(count=Count('initial'))
.order_by('initial'))
В результате получится что-то вроде
<QuerySet [{'initial': 'A', 'count': 1},
{'initial': 'B', 'count': 2},
...
{'initial': 'Y', 'count': 1}]>