Может ли Django делать вложенные запросы и исключения - PullRequest
1 голос
/ 02 марта 2010

Мне нужна помощь в составлении этого запроса в Django. Я упростил приведенный здесь пример, чтобы просто перейти прямо к сути.

MyModel(models.Model):
    created = models.DateTimeField()
    user = models.ForeignKey(User)
    data = models.BooleanField()

Запрос, который я хотел бы создать на английском языке, будет звучать так:

Дайте мне каждую созданную вчера запись, для которой данные имеют значение Ложь, когда в этом же диапазоне данные никогда не отображаются как Истина для данного пользователя

Вот пример ввода / вывода на случай, если неясно.

Табличные значения

ID   Created    User    Data

1    1/1/2010   admin   False
2    1/1/2010   joe     True
3    1/1/2010   admin   False
4    1/1/2010   joe     False
5    1/2/2010   joe     False

Выходной набор запросов

1    1/1/2010   admin   False
3    1/1/2010   admin   False

Что я хочу сделать, так это исключить запись № 4. Причина этого заключается в том, что в заданном диапазоне «вчера» данные отображаются как Истинные один раз для пользователя в записи № 2, поэтому это исключило бы запись № 4.

В некотором смысле кажется, что происходит 2 запроса. Один для определения записей в заданном диапазоне и один для исключения записей, которые пересекаются с «истинными» записями.

Как я могу сделать этот запрос с Django ORM?

Ответы [ 3 ]

5 голосов
/ 02 марта 2010

Вам не нужен вложенный запрос. Вы можете создать список PK плохих пользователей, а затем исключить записи, содержащие эти PK, в следующем запросе.

bad = list(set(MyModel.obejcts.filter(data=True).values_list('user', flat=True)))
# list(set(list_object)) will remove duplicates
# not needed but might save the DB some work

rs = MyModel.objects.filter(datequery).exclude(user__pk__in=bad)
# might not need the pk in user__pk__in - try it

Вы могли бы сжать это в одну строку, но я думаю, что это так аккуратно, как вы получите. 2 запроса не так уж и плохо.

Редактировать: Вы можете прочитать документы по этому вопросу:

http://docs.djangoproject.com/en/dev/ref/models/querysets/#in

Звучит так, будто он автоматически вкладывает запрос (поэтому в базе данных запускается только один запрос), если он выглядит так:

bad = MyModel.objects.filter(data=True).values('pk')
rs  = MyModel.objects.filter(datequery).exclude(user__pk__in=bad)

Но MySQL не так хорошо оптимизирует, поэтому мой приведенный выше код (2 полных запроса) может на самом деле работать намного быстрее.

Попробуйте оба и гоните их!

0 голосов
/ 02 марта 2010

Благодаря отложенной оценке вы можете разбить ваш запрос на несколько разных переменных, чтобы их было легче читать. Вот немного ./manage.py shell времени воспроизведения в стиле, который Оли уже представил.

> from django.db import connection
> connection.queries = []
> target_day_qs = MyModel.objects.filter(created='2010-1-1')
> bad_users = target_day_qs.filter(data=True).values('user')
> result = target_day_qs.exclude(user__in=bad_users)
> [r.id for r in result]
[1, 3]
> len(connection.queries)
1

Вы также можете сказать result.select_related(), если хотите добавить объекты пользователя в том же запросе.

0 голосов
/ 02 марта 2010

похоже, что вы могли бы использовать: из django.db.models import F MyModel.objects.filter(datequery).filter(data=False).filter(data = F('data'))

F объект доступен с версии 1.0

Пожалуйста, проверьте это, я не уверен.

...