Различные результаты запроса, изменяющего порядок объектов Q

У меня возникла проблема при создании набора запросов с использованием объектов Q. Я получаю разные результаты в зависимости от того, как я заказываю некоторые условия Q. Я немного упрощу свои модели, чтобы четко описать мою проблему.

class D(models.Model):
    one_attr = models.BooleanField()
    s_attr = models.ManyToManyField(S, through='DRelatedToS')
    d_to_p = models.ForeignKey(P)


class S(models.Model):
    other_attr = models.BooleanField()
    s_to_p = models.ForeignKey(P)


class DRelatedToS(models.Model):
    to_s = models.ForeignKey(S)
    to_d = models.ForeignKey(D)
    date = models.DateField()


class P(models.Model):
    the_last_attr = models.PositiveIntegerField()

Резюме отношений:

D <-- DRelatedToS --> S --> P
|                           ^
|                           |
-------->------->------>----^

С этими моделями и отношениями я получаю два разных результата в зависимости от того, как я упорядочиваю Q-условия: Первый запрос, который дает один результат

D.objects.filter(
    Q(one_attr=True, s_attr__s_to_p__the_last_attr=5)
    |
    Q(one_attr=False, d_to_p__the_last_attr=10)
)

Второй запрос дает другой результат, отличный от первого запроса

D.objects.filter(
    Q(one_attr=False, d_to_p__the_last_attr=10)
    |
    Q(one_attr=True, s_attr__s_to_p__the_last_attr=5)
)

Мой вопрос: почему это происходит? Есть ли какие-либо проблемы с тем, как я выполняю свой запрос?

Когда я смотрю операторы SQL, полученные из этих запросов, я получаю два разных оператора: один делает LEFT OUTER JOIN и много INNER JOIN, а второй дает все INNER JOIN. Тот, который на самом деле возвращает то, что я хочу, это тот, который делает LEFT OUTER JOIN. Это заставляет меня чувствовать, что все мои запросы могут возвращать плохие результаты в зависимости от того, как я устрою их условия. Это баг или я что-то (или все) делаю не так?


person marianobianchi    schedule 07.11.2013    source источник
comment
Упрощение моделей — это Хорошая вещь™   -  person Chris Wesseling    schedule 20.11.2013


Ответы (3)


В вашем примере другой порядок должен возвращать одинаковый результат.

Тем не менее я проверил ваш код (используя исправления, которые я внес в коды вопросов), но не могу сгенерировать описанную вами ошибку. Возможно, вы допустили другие ошибки и пропустили при упрощении кода, не могли бы вы опубликовать образцы данных, которые вы использовали? (см. мои данные ниже).

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

Исправление 1. Модель изменяется в формате diff:

3,4c6,10
<     s_attr = models.ManyToMany(S, through='DRelatedToS')
<     d_to_p = models.ForeignKey(P)
---
>     s_attr = models.ManyToManyField('S', through='DRelatedToS')
>     d_to_p = models.ForeignKey('P')
> 
>     def __unicode__(self):
>         return 'D:(%s,%s,%s)' % (self.id, self.one_attr, self.d_to_p.the_last_attr)
8,9c14
<     other_attr = models.BooleanField()
<     s_to_p = models.ForeignKey(P)
---
>     s_to_p = models.ForeignKey('P')
13d17
<     to_p = models.ForeignKey(P)
15c19
<     date = models.DateField()
---
>     to_s = models.ForeignKey(S)
19a24
> 

Итак, после внесения исправлений модели выглядят так:

class D(models.Model):
    one_attr = models.BooleanField()
    s_attr = models.ManyToManyField('S', through='DRelatedToS')
    d_to_p = models.ForeignKey('P')

    def __unicode__(self):
        return 'D:(%s,%s,%s)' % (self.id, self.one_attr, self.d_to_p.the_last_attr)


class S(models.Model):
    s_to_p = models.ForeignKey('P')


class DRelatedToS(models.Model):
    to_d = models.ForeignKey(D)
    to_s = models.ForeignKey(S)


class P(models.Model):
    the_last_attr = models.PositiveIntegerField()

Исправление 2. Ваши поля поиска в запросах неверны (Исправлено в ответе).

Я сделал следующее:

  1. Создайте проект и приложение с именем testso:

    django-admin.py startproject marianobianchi
    cd marianobianchi
    python manage.py startapp testso
    
  2. Добавьте свои модели и настройте параметры проекта (настройки базы данных, добавьте testso к INSTALLED_APPS)

  3. Добавьте образцы данных:

    mkdir testso/fixtures
    cat > testso/fixtures/initial_data.json
    [
        {"pk": 1, "model": "testso.d", "fields": {"one_attr": true, "d_to_p": 3}},
        {"pk": 2, "model": "testso.d", "fields": {"one_attr": true, "d_to_p": 4}},
        {"pk": 3, "model": "testso.d", "fields": {"one_attr": false, "d_to_p": 5}},
        {"pk": 4, "model": "testso.d", "fields": {"one_attr": false, "d_to_p": 5}},
    
        {"pk": 1, "model": "testso.s", "fields": {"s_to_p": 1}},
        {"pk": 2, "model": "testso.s", "fields": {"s_to_p": 2}},
        {"pk": 3, "model": "testso.s", "fields": {"s_to_p": 3}},
    
        {"pk": 1, "model": "testso.drelatedtos", "fields": {"to_d": 2, "to_s": 1}},
        {"pk": 2, "model": "testso.drelatedtos", "fields": {"to_d": 1, "to_s": 2}},
        {"pk": 3, "model": "testso.drelatedtos", "fields": {"to_d": 1, "to_s": 3}},
    
        {"pk": 1, "model": "testso.p", "fields": {"the_last_attr": 5}},
        {"pk": 2, "model": "testso.p", "fields": {"the_last_attr": 5}},
        {"pk": 3, "model": "testso.p", "fields": {"the_last_attr": 3}},
        {"pk": 4, "model": "testso.p", "fields": {"the_last_attr": 4}},
        {"pk": 5, "model": "testso.p", "fields": {"the_last_attr": 10}}
    ]
    
  4. python manage.py syncdb

  5. python manage.py shell
  6. В оболочке:

    >>> from testso.models import *
    >>> from django.db.models import Q
    >>> D.objects.filter(Q(one_attr=True, s_attr__s_to_p__the_last_attr=5) | Q(one_attr=False, d_to_p__the_last_attr=10))
    [<D: D:(1,True,3)>, <D: D:(2,True,4)>, <D: D:(3,False,10)>, <D: D:(4,False,10)>]
    >>> D.objects.filter(Q(one_attr=False, d_to_p__the_last_attr=10) | Q(one_attr=True, s_attr__s_to_p__the_last_attr=5))
    [<D: D:(1,True,3)>, <D: D:(2,True,4)>, <D: D:(3,False,10)>, <D: D:(4,False,10)>]
    

Тот же результат! ...как и ожидалось.

person juliocesar    schedule 16.11.2013
comment
Спасибо за Ваш ответ! Я попробую это позже, чтобы увидеть, работает ли это. Что касается ваших исправлений, я отредактировал свой вопрос, чтобы исправить свои ошибки, но некоторые из них не были ошибками (например, «P» вместо P) ... Если вы хотите, отредактируйте свой ответ, чтобы сделать его короче. Я хотел принять ваше редактирование, но я думаю, что у меня нет необходимой репутации... - person marianobianchi; 17.11.2013
comment
Ссылка на модель, которую вы не определили ранее, вызывает исключение, поэтому вам нужно «P» вместо P ... Я снова предложу исправления, я думаю, вам не нужна репутация, чтобы принять это предложение;) - person juliocesar; 17.11.2013
comment
внесенные изменения в предложение, после того, как я увижу ваш вопрос обновленным, я удалю его из своего ответа - person juliocesar; 17.11.2013
comment
хорошо... я понимаю, что вы говорите, но вы знаете, что проблема не в этом... Что касается вашего предложения, я вижу уведомление о предложенном издании, но когда я нажимаю на него, я вижу ваши изменения, но их нет кнопку, чтобы принять их.... - person marianobianchi; 17.11.2013
comment
Вы должны увидеть кнопки для утверждения/отклонения предложения по редактированию, возможно, это и это помогут вам. Вы должны думать, что это не решает вашу проблему, но помогает людям запускать ваш тестовый пример, если он не работает, как кто-то может попытаться его решить? Я исправил это и предложил изменение, чтобы упростить поиск решений не только мной. - person juliocesar; 17.11.2013
comment
ок... там написано, что оно было отклонено 18 часов назад тремя другими пользователями... :( - person marianobianchi; 17.11.2013
comment
Похоже, рецензентам не нравятся изменения в коде. Я удалю исправление 2, как я вижу, исправлено в вашем ответе. - person juliocesar; 18.11.2013

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

subset_a = D.objects.filter(one_attr=False, d_to_p__the_last_attr=10)
subset_b = D.objects.filter(one_attr=True, s_attr__p__the_last_attr=5)
union_set = subset_a | subset_b
union_set = union_set.distinct()

| оператор на двух наборах запросов выполняет объединение, а отдельный гарантирует, что вы не получите повторяющиеся строки. Мне было бы интересно услышать, имеет ли здесь значение и аранжировка.

person Krystian Cybulski    schedule 13.11.2013
comment
Спасибо за Ваш ответ! Но я не думаю, что это работает. В то время как subset_a и subset_b дают мне 25 и 0 результатов, когда я делаю subset_a | subset_b или subset_b | subset_a, я получаю тот же результат: 0 (ноль.... пустой набор). Помимо этих результатов, я не нашел официального комментария на странице проекта django об этом операторе между двумя наборами запросов... вы уверены, что он работает так, как вы сказали? - person marianobianchi; 15.11.2013
comment
Я использовал | оператор на QuerySets несколько раз, и это сработало для меня. Я не нашел официальных документов по этому вопросу, но другие ответы StackOverflow подтверждают мое предположение: stackoverflow.com/questions/4411049/ . Вы видите результат str(subset_a.query), str(subset_b.query) и str(union_set.query)? - person Krystian Cybulski; 15.11.2013
comment
Документация для | docs.djangoproject.com/en/dev /topics/db/queries/ - person Martin Krung; 20.04.2017

Похоже, у Джанго есть слабая реализация ORM. Я бы предложил использовать «фильтр» или какой-либо другой способ запроса базы данных и посмотреть, получите ли вы те же несоответствия.

Или, возможно, вам следует поискать альтернативные реализации ORM, такие как peewee.

person OCarneiro    schedule 13.11.2013