Группировка Django ORM по датам рождения

#python #django #postgresql-9.3

#python #django #postgresql-9.3

Вопрос:

Я действительно изо всех сил пытался найти хороший способ группировать по диапазону дат, используя ORM django. У меня есть пользователь, у которого есть поле для даты рождения, и я хотел бы сгруппировать всех пользователей в возрасте 10-15 лет, 15-20 лет и так далее и вернуть группу и общее количество людей в этом возрастном диапазоне.

Моя модель:

 class Reader(models.Model):
    user = models.OneToOneField(User)
    birth_date = models.DateField()
  

Мой необработанный sql выглядит так:

  SELECT count(*) as total,
   CASE
     WHEN EXTRACT(year from AGE(NOW(), birth_date)) <= 10 THEN '1-10'
     WHEN EXTRACT(year from AGE(NOW(), birth_date)) <= 15 THEN '11-15'
     WHEN EXTRACT(year from AGE(NOW(), birth_date)) <= 20 THEN '16-20'
     WHEN EXTRACT(year from AGE(NOW(), birth_date)) <= 25 THEN '21-25'
     WHEN EXTRACT(year from AGE(NOW(), birth_date)) <= 30 THEN '26-30'
     WHEN EXTRACT(year from AGE(NOW(), birth_date)) <= 40 THEN '31-40'
     WHEN EXTRACT(year from AGE(NOW(), birth_date)) <= 50 THEN '41-50'
     WHEN EXTRACT(year from AGE(NOW(), birth_date)) <= 60 THEN '51-60'
     WHEN EXTRACT(year from AGE(NOW(), birth_date)) > 60 THEN '60 '
   END as age
   FROM main_reader AS reader
   GROUP BY age
  

Ответ №1:

Возможно, это будет полезно будущим искателям.

Теперь мы можем решить этот вопрос с помощью условных выражений Case() When() и функции агрегирования Count() :

Также здесь я использую relativedelta для вычисления дат.

Вот пример:

 
from django.utils import timezone
from django.db.models import Count, Case, When
from dateutil.relativedelta import relativedelta


current_date = timezone.now().date()

range_ages = (
    {"lookup": "gte", "label": "-17", "age": [18]},
    {"lookup": "range", "label": "18-24", "age": [18, 25]},
    {"lookup": "range", "label": "25-34", "age": [25, 35]},
    {"lookup": "range", "label": "35-44", "age": [35, 45]},
    {"lookup": "range", "label": "45-54", "age": [45, 55]},
    {"lookup": "range", "label": "55-64", "age": [55, 65]},
    {"lookup": "lt", "label": "65 ", "age": [65]},
)

aggr_query = {}
for item in range_ages:
    age = item.get("age")
    lookup = item.get("lookup")
    label = item.get("label")
    # calculate start_date an end_date
    end_date = current_date - relativedelta(years=age[0])
    start_date = current_date - relativedelta(years=age[-1], days=-1)
    f_value = start_date if len(age) == 1 else (start_date, end_date)
    if lookup == "gte":
        aggr_query[label]=Count(Case(When(date_of_birth__gte=f_value, then=1)))
    elif lookup == "lt":
        aggr_query[label]=Count(Case(When(date_of_birth__lt=f_value, then=1)))
    else:
        aggr_query[label]=Count(Case(When(date_of_birth__range=f_value, then=1)))

#Aggregate values
qs_values = MyModel.objects.filter(gender=gender).aggregate(**aggr_query)
  

Результат будет выглядеть следующим образом:

 {'55-64': 1726, '25-34': 2590, '65 ': 5691, '18-24': 517, '45-54': 1209, '-17': 0, '35-44': 2416}
  

Мы также можем использовать annotate() его, возвращая нам набор запросов объектов.

Ответ №2:

Может быть, попробуйте что-то подобное (не тестировалось):

 case_when_query = "(case when extract...end)" # Your case when query here

extra_qs = Reader.objects.extra(select={'count': 'count(1)', 'age': case_when_query})

query_set = extra_qs.values('count', 'age')
query_set.query.group_by = ['age']