Обычно это ответ для набора запросов с двумя или более условиями, связанными с дочерними объектами.
Решение : возможно простое решение с двумя подзапросами ,даже без какого-либо соединения:
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 для более сложных случаев, но нам не нужно было это для этого простоговопрос.)