#5763: Queryset doesn't have a "not equal" filter operator
-------------------------------------+-------------------------------------
     Reporter:  jdetaeye             |                    Owner:  nobody
         Type:  New feature          |                   Status:  reopened
    Component:  Database layer       |                  Version:  SVN
  (models, ORM)                      |               Resolution:
     Severity:  Normal               |             Triage Stage:  Design
     Keywords:  qs-rf                |  decision needed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  1                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Changes (by asmoore82):

 * status:  closed => reopened
 * resolution:  wontfix =>
 * easy:  0 => 1


Comment:

 Apologies for the re-open - but I wasn't sure what would be worse, a re-
 open or a new ticket for the same old discussion...

 I'm going to have to take the position that the absence of a `__ne`
 operator //still//
 represents a functional hole in the querying API, even with consideration
 of exclude()

 > it doesn't make sense to have `__ne` without any other negated queries,

 Ah but there are indeed other negated queries.

 `filter(__gte)` and `exclude(__lt)` are **almost** equivalent.

 Similarly, you can **almost** approximate `__ne` with a hokey `Q(__lt) |
 Q(__gt)` contraption.

 But the real issue lies within that "almost" -- this is what could be
 cause for a separate ticket, but a quick and dirty `__ne` would head the
 issue off altogether ;). Chaining multiple `filter()` and `exclude()`
 calls on multi-valued relationships yields not-so-surprising but
 nonetheless undesired results. To quote from the Django docs:

 > Django has a consistent way of processing `filter()` and `exclude()`
 calls. Everything inside a single `filter()` call is applied
 simultaneously to filter out items matching all those requirements.
 Successive `filter()` calls further restrict the set of objects, but for
 multi-valued relations, they apply to any object linked to the primary
 model, not necessarily those objects that were selected by an earlier
 `filter()` call.

 This consistency is a good thing but it combines with the lack of `__ne`
 to form a problem.

 Say you have blogs with entries with tags and author_counts

 If you want to get all entries that are tagged 'django' with more than 2
 co-authors:
  {{{ Entry.objects.filter(tag__name='django', author_count__gt=2) }}}

 It is similarly easy to get blogs with entries with the above criteria:
  {{{ Blog.objects.filter(entry__tag__name='django',
 entry__author_count__gt=2) }}}

 But what if you want entries tagged 'django' that are **not** co-authored
 by 2:
  {{{ Entry.objects.filter(tag__name='django').exclude(author_count=2) }}}

 But if you want the blogs with those entries, it can't easily be done:
  {{{
 Blog.objects.filter(entry__tag__name='django').exclude(entry__author_count=2)
 }}}
 is **not** the equivalent of the imaginary query:
  {{{ Blog.objects.filter(entry__tag__name='django',
 entry__author_count__ne=2) }}}
 which can currently be approximated with:
  {{{ Blog.objects.filter(Q(entry__author_count__lt=2) |
 Q(entry__author_count__gt=2), entry__tag__name='django') }}}

 You can also get the desired results with a `.extra()` call but let's not
 go there :P.

 This brings us to the most surprising aspect, using the negation operator
 `~` on `Q()` objects that select on multi-valued relations is technically
 possible but can yield the undesired results on the unsuspecting, which
 actually runs sort of contrary to the Documentation quote above.

 Bad Surprise!!:
  {{{ Blog.objects.filter(~Q(entry__author_count=2),
 entry__tag__name='django') }}}
 `^`The more I look at this, the more I think it is cause for a ticket in
 its own right. The fix for this would be to smarten up the `Q()` objects
 so that a NOT operator on `__gt` becomes `__lte`, a NOT on `__lt` becomes
 `__gte`, and so on. But you will ultimately be missing the `__ne` and
 other NOT primitives to fall back on.

 In other words, this ticket is a "could-go-either-way" blocker for `^`that
 more important ticket. I'll do some research on that and make a ticket if
 it hasn't already been addressed.

 Thanks to all who make Django awesome! I'm a database newbie and loving
 it!

 ~Adam sM

-- 
Ticket URL: <https://code.djangoproject.com/ticket/5763#comment:14>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to django-updates@googlegroups.com.
To unsubscribe from this group, send email to 
django-updates+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to