2013-11-07 4 views
2

У меня возникла проблема при создании запроса с использованием объектов 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. Это заставляет меня чувствовать, что все мои запросы могут возвращать плохие результаты в зависимости от того, как я устрою его условия. Является ли это ошибкой или я что-то делаю (или все) неправильно?

+0

Упрощение ваших моделей - [Good Thing ™] (http://sscce.org/) –

ответ

0

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

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

2

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

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() 

| оператор на двух запросах делает объединение, а четвёрка будет следить за тем, чтобы вы не попадали в строки dupe. Мне было бы интересно узнать, имеет ли место здесь и порядок.

+0

Спасибо за ваш ответ! Но я не думаю, что это работает. Хотя subset_a и subset_b дают мне результаты 25 и 0, когда я делаю 'subset_a | subset_b' или 'subset_b | subset_a' i получает тот же результат: 0 (нуль .... пустой набор). Помимо этих результатов, я не могу найти официальный комментарий на странице проекта django об этом операторе между двумя запросами ... вы уверены, что это работает, как вы сказали? – marianobianchi

+0

Я использовал | оператор на QuerySets несколько раз, и он работал для меня. Я не нашел официальных документов python по этому поводу, но другие ответы StackOverflow подтверждают мое предложение: http://stackoverflow.com/questions/4411049/how-can-i-find-the-union-of-two-django-querysets. Вы видите, что такое результат 'str (subset_a.query)', 'str (subset_b.query)' и 'str (union_set.query)'? –

+0

Документация для | https://docs.djangoproject.com/ru/dev/topics/db/queries/#complex-lookups-with-q –

3

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

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

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

Correction 1: модель изменения в формате дифф:

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)>] 
    

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

+2

Спасибо за ваш ответ! Я попробую это позже, чтобы увидеть, работает ли это. О ваших исправлениях я редактировал свой вопрос, чтобы исправить свои ошибки, но некоторые из них не были ошибками (например, «P» вместо P) ... Если вы хотите отредактировать свой ответ, чтобы сделать его короче. Я хотел принять ваше редактирование, но я думаю, что у меня нет необходимой репутации ... – marianobianchi

+0

Ссылка на модель, которую вы не определили до того, как возникло исключение, поэтому вам нужно «P» inst ... Я буду предлагать исправления снова, я думаю, что вам не нужна репутация, чтобы принять это предложение;) – juliocesar

+0

Изменения в предложениях внесены, после того как я увижу, что ваш вопрос обновлен, я удалю из своего ответа – juliocesar

Смежные вопросы