As far as I understand, this is exactly the query you want. The filters
are treated as single call, that is, they should target the same row,
not possibly different rows of the multijoin. It is another matter if it
actually works in current ORM implementation. IIRC something like
filter(~Q(pk=1)) and .exclude(Q(pk=1)) can produce different results.
But they _should_ produce the same result, and if they do not,
introducing negated lookups isn't the way to fix this - the correct
thing to do is fixing the ORM.
- Anssi
I do see a problem here: the equality ~Q(a=1) <-> Q(a__lt=1)|Q(a__gt=1) is
not correct in m2m situations: the first is asking for rows where a must not
be 1, most of all, if there is no a, it is a match. The other is asking for rows
where there must be an A value, and it must be < 1 or > 1, most of all, if
there is no value at all, it is NOT a match. So:
filter(~Q(a=1), a__isnull=False) <-> Q(a__lt=1)|Q(a__gt=1).
The ORM is not able to handle the first version correctly. The interpretation
would be that there is at least one 'a' row, and its value is not 1.
I am strongly against the idea that Q(a__neq=1) would have different
interpretation of ~Q(a__eq=1). If they would have different interpretation, then
there would be basis for negative lookups. Although AFAICS you could still get
the same results using ~Q(a__eq=1, a__isnull=False) so the API would still work
without negative lookups.
I am basing the following discussion on the assumption that a__neq and ~a__eq
should be the same thing.
From ORM API standpoint, the claim that .exclude() can never obsolete
direct negative lookups is wrong as far as I understand the problem.
Reason:
.filter(Q(__neq=val)) <-> .filter(~Q(__exact=val)) <->
.exclude(Q(__exact=val))
Another way to see this is that Django should return same results for
the queries:
filter(~Q(employment__school__site_name='RAE'), employment__end_date=None)
and
filter(employment__school__site_name__neq='RAE', employment__end_date=None)
However, I do not think your issue is due to the above equality between
the two different ways of writing ~__eq problem, it is due to a bug in ORM
implementation. The second filter condition is not pushed down to the
subquery generated by the negated Q condition, and thus it generates
another join and potentially targets different rows. I think this is the main
problem in your situation. This is reinforce by this snippet from your query:
WHERE (
NOT
`data_staff`.`id`
IN ( subquery data_employment U1)
-- different data_employment reference from the subquery
AND `data_employment`.`end_date` IS NULL
)
That is, you have the data_employment table two times in the query, and
thus the filters are targeting potentially different rows. Note that this is a
bug. The conditions are in the same .filter() call, and thus they should target
the same row!
IMHO There are two underlying problems in the ORM related to this matter,
one is detecting when to use a subquery for the filter condition. The logic
for that is easily fooled. Another problem is that if you do a subquery,
other conditions that should go into the subquery WHERE are sometimes
not correctly pushed down to the subquery clause. This is similar to HAVING
clause pushdown problem.
I must say the m2m handling is very complicated, it took some time to see
the ~__eq=1 <-> __lt=1|__gt=1 difference for example... Thus, it is likely
that I am missing something else, too.
- Anssi
On Fri, Nov 30, 2012 at 1:29 PM, Marek Brzóska <brzosk...@gmail.com> wrote:What precisely is wrong with:
> Has the matter been completely put away?
>
> I would like to bring it up again.
>
> I have Articles and Categories. An Article belongs to Categories:
>
> class Category(model):
> pass
> class Article(model):
> category = ManyToManyField(Category)
> status = CharField(max_length=10)
>
> Now I want all categories with articles that have status different than
> "archived".
>
> I cannot do this with django's ORM!
>
> Category.objects.filter(~Q(article_status="archived"))
Category.objects.exclude(article_status='archived')
--
You received this message because you are subscribed to the Google Groups "Django developers" group.
To post to this group, send email to django-d...@googlegroups.com.
To unsubscribe from this group, send email to django-develop...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/django-developers?hl=en.
That's a very different question:>> What precisely is wrong with:
>>
>> Category.objects.exclude(article_status='archived')
>
> It excludes all categories that have at least one archived article.
>
> And I want categories that have at least one NOT archived article.
>
> Example: I have one category: politics. I have two articles in this
> category: "Vote Obama!" which archived and "U.S wars" which is not archived.
> Category.objects.exclude(article_status='archived')
> will show no categories, while I want my only category to show, because
> there is one not archived article, "U.S. wars".
Category.objects.exclude(article__status='archived').annotate(num_articles=Count('article')).filter(num_articles__gt=0)
Still answerable via the ORM.
Cheers
Tom
live_articles = Article.objects.exclude(status="archived")
live_cats = Category.objects.filter(article__in=live_articles)