Django Q Queries & на том же поле? - PullRequest
2 голосов
/ 28 марта 2019

Итак, вот мои модели:

class Event(models.Model):
    user = models.ForeignKey(User, blank=True, null=True, db_index=True)
    name = models.CharField(max_length = 200, db_index=True)
    platform = models.CharField(choices = (("ios", "ios"), ("android", "android")), max_length=50)

class User(AbstractUser):
    email = models.CharField(max_length=50, null=False, blank=False, unique=True)

Event похоже на событие аналитики, поэтому вполне возможно, что у меня может быть несколько событий для одного пользователя, некоторые с platform=ios и некоторые с platform=android, если пользователь вошел в систему на нескольких устройствах. Я хочу запросить, чтобы увидеть, сколько пользователей имеют устройства IOS и Android. Поэтому я написал такой запрос:

User.objects.filter(Q(event__platform="ios") & Q(event__platform="android")).count()

, который возвращает 0 результатов. Я знаю, что это не правильно. Тогда я подумал, что попробую просто запросить пользователей iOS:

User.objects.filter(Q(event__platform="ios")).count()

, который дал 6 717 622 результатов, что является неожиданным, потому что у меня есть только 39 294 пользователя. Я предполагаю, что это не подсчет пользователей, а подсчет экземпляров Event, что мне кажется неправильным поведением. У кого-нибудь есть понимание этой проблемы?

Ответы [ 2 ]

2 голосов
/ 28 марта 2019

Вместо этого вы можете использовать аннотации:

django.db.models import Count

User.objects.all().annotate(events_count=Count('event')).filter(events_count=2)

Таким образом, он отфильтрует любого пользователя, у которого есть два события.

Вы также можете использовать цепные фильтры:

User.objects.filter(event__platform='android').filter(event__platform='ios')

Какой первый фильтр получит всех пользователей с платформой Android, а второй - пользователей с платформой iOS.

0 голосов
/ 04 апреля 2019

Обычно это ответ для набора запросов с двумя или более условиями, связанными с дочерними объектами.

Решение : возможно простое решение с двумя подзапросами ,даже без какого-либо соединения:

base_subq = Event.objects.values('user_id').order_by().distinct()
user_qs = User.objects.filter(
    Q(pk__in=base_subq.filter(platform="android")) &
    Q(pk__in=base_subq.filter(platform="ios"))
)

Метод .order_by() важен, если для модели Event задано упорядочение по умолчанию (см. его в документации по методу Different ()).


Примечания :

Проверьте единственный запрос SQL, который будет выполнен: (упрощается удалением префикса "app_".)

>>> print(str(user_qs.query))
SELECT user.id, user.email FROM user WHERE (
    user.id IN (SELECT DISTINCT U0.user_id FROM event U0 WHERE U0.platform = 'android')
    AND
    user.id IN (SELECT DISTINCT U0.user_id FROM event U0 WHERE U0.platform = 'ios')
)
  • Функция Q() используется потому, что один и тот же параметр условия (pk__in) не может повторяться в одном и том же filter(), но вместо него можно использовать также цепочечные фильтры: .filter(...).filter(...).(Порядок условий фильтрации не важен, и он перевешивается предпочтениями, оцененными оптимизатором SQL-сервера.)
  • Временная переменная base_subq является набором запросов «псевдонимов» только для того, чтобы не повторять ту же частьВыражение, которое никогда не оценивается индивидуально.
  • Одно соединение между пользователем (родительским) и событием (дочерним) не будет проблемой, и решение с одним подзапросом также возможно, но соединение с событием и событием (aобъединение с повторяющимся дочерним объектом или с двумя дочерними объектами) следует избегать подзапросом в любом случае.Два подзапроса хороши для удобства чтения, чтобы продемонстрировать симметрию двух условий фильтрации.

Другое решение с двумя вложенными подзапросами Это несимметричное решение может быть быстрее, если мы знаемэтот один подзапрос (который мы помещаем вглубь) имеет гораздо более строгий фильтр, чем другой необходимый подзапрос с огромным набором результатов.(пример, если число пользователей Android будет огромным)

ios_user_ids = (Event.objects.filter(platform="ios")
                .values('user_id').order_by().distinct())
user_ids = (Event.objects.filter(platform="android", user_id__in=ios_user_ids)
            .values('user_id').order_by().distinct())
user_qs = User.objects.filter(pk__in=user_ids)

Проверьте, как он скомпилирован в SQL: (снова упростите, удалив префикс app_ и ".)

>>> print(str(user_qs.query))
SELECT user.id, user.email FROM user 
WHERE user.id IN (
    SELECT DISTINCT V0.user_id FROM event V0
    WHERE V0.platform = 'ios' AND V0.user_id IN (
        SELECT DISTINCT U0.user_id FROM event U0
        WHERE U0.platform = 'android'
    )
)

(Эти решения работают и в старом Django, например, 1.8. Специальная функция подзапроса Subquery() существует с Django 1.11 для более сложных случаев, но нам не нужно было это для этого простоговопрос.)

...