简体   繁体   中英

How to obtain a list of all distinct first letters of given field

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: ABD 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:

  1. Get all people
  2. Create set (because of the uniqueness of elements) of the first letters
  3. 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?

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}]>

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.

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))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM