Let's say I have this Person class that consist of last_name string field.
I would like to display links of all first letters of names existing in db.
So for example:
A B D E...
when there's Adams, Brown, Douglas, Evans and no one that last_name starts with C.
Of course view is not a problem here as I want to prepare all of this on backend. So the question is how to write good model's or view's function that will provide this.
I would like it to be DB-independent, however tricks for any particular DB would be a bonus.
Also, I would like this to be quite well optimized in terms of speed because it could be a lot of names. It should be less naive than this most simple algorithm:
- Get all people
- Create set (because of the uniqueness of elements) of the first letters
- Sort and return
So for example (in views.py):
names = Person.objects.values_list('last_name', flat=True)
letters = {name[0] for name in names}
letters_sorted = sorted(letters)
Of course I could order_by first or assign new attribute to each objects (containing first letter) but I don't think it will speed up the process.
I also think that assuming that all letters are in use is bad assumption if I would go check each letter if at least one name for this letter exists ;-)
Which approach would be best effective for databases and django here?
CodePudding user response:
I would sort the names first, then unpack the names as arguments for zip
, then consume just the first tuple that zip
yields:
names = sorted(Person.objects.values_list('last_name', flat=True))
first_letters = next(zip(*names))
This doesn't use sets or remove duplicates or anything like that. Is that critical? If it is, you could do this:
names = Person.objects.values_list('last_name', flat=True)
first_letters = sorted(set(next(zip(*names))))
Though this would be hardly more performant that what you've already written.
CodePudding user response:
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))
CodePudding user response:
You could also use the orm methods to generate the list of initals (including 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'))
This will result in something like
<QuerySet [{'initial': 'A', 'count': 1},
{'initial': 'B', 'count': 2},
...
{'initial': 'Y', 'count': 1}]>