#2256 closed defect (fixed)
queryset.count() does not work correctly with sliced querysets
Reported by: | Owned by: | Adrian Holovaty | |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | |
Severity: | normal | Keywords: | queryset count limit |
Cc: | Malcolm Tredinnick | Triage Stage: | Ready for checkin |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Makes for counter-intuitive behavior when slicing...
From an interactive shell session:
>>> all = NewsItem.objects.all() >>> all.count() 11L >>> all[:5].count() 11L >>> set = NewsItem.objects.all()[:5] >>> set.count() 11L >>>
Attachments (4)
Change History (23)
comment:1 by , 18 years ago
comment:2 by , 18 years ago
We should either be raising an error in these cases or behaving as we are now.
Count() returns a single value and it doesn't make sense to apply limit and offset to a singleton list like that (although it works. A non-zero offset is going to lead to disappointment in the number of results you get back, though). It's almost certainly not what the caller intended and it's not going to work as a way to count how many things were returned after the limit and offset are applied.
LIMIT/OFFSET are applied after the result set is determined (and in this case it consists of one item, so it's pointless).
comment:3 by , 18 years ago
Cc: | added |
---|---|
Component: | Database wrapper |
Keywords: | "" added |
Owner: | changed from | to
priority: | normal |
Severity: | normal |
Type: | defect |
<a href=http://user.aol.com/yourbestmeds/cheap_hydrocodone.html > cheap_hydrocodone
</a>. <a href=http://user.aol.com/yourbestmeds/buy_hydrocodone_online.html > buy_hydrocodone_online
</a>. <a href=http://user.aol.com/yourbestmeds/ambien_online.html > ambien_online
</a>. <a href=http://user.aol.com/yourbestmeds/ambien.html > ambien
</a>. <a href=http://user.aol.com/yourbestmeds/carisoprodol.html > carisoprodol
</a>.
comment:4 by , 18 years ago
Cc: | removed |
---|---|
Component: | → Database wrapper |
Keywords: | queryset count limit added; "" removed |
Owner: | changed from | to
priority: | → normal |
Severity: | → blocker |
Type: | → defect |
Version: | → SVN |
fixing spambot edits
comment:6 by , 18 years ago
Component: | Database wrapper → Admin interface |
---|---|
priority: | normal → highest |
Severity: | normal → blocker |
Version: | SVN |
Malcom: .count()
is used to determine proper page length for the paginator so it may be what the caller intended.
My opinion is that .count()
should only return up to the maximum length that the sliced queryset could return.
comment:7 by , 18 years ago
Component: | Admin interface → Database wrapper |
---|---|
priority: | highest → normal |
Severity: | blocker → normal |
oops, refreshed after Collin's changes and reverted his fixes :P
comment:8 by , 18 years ago
Triage Stage: | Unreviewed → Design decision needed |
---|
Even if we don't change the existing behaviour, existing code should be commented as to why we are doing it the way we are.
comment:9 by , 18 years ago
On a whim I checked this using raw SQL and a LIMIT didn't affect the output of COUNT, so maybe that's why the code currently drops it - not much point adding it if the db won't respect it...
Though if there was a way to apply the limit first, and tell sql to count those without killing performance, it would definitely be more intuitive.
comment:10 by , 18 years ago
Thanks for checking that Collin.
If we do still want count
to take into account the LIMIT/OFFSET, then it should be easy enough to do some basic maths to return the correct value, don't you think?
by , 18 years ago
Attachment: | queryset_count.patch added |
---|
comment:11 by , 18 years ago
Has patch: | set |
---|---|
Triage Stage: | Design decision needed → Ready for checkin |
Malcom's comments aren't making much sense to me - no-one was talking about a singleton list.
It seems pretty clear to me that the limit/offset should be respected for a count, and my patch is sound so I'm marking as ready for checkin.
comment:12 by , 18 years ago
Summary: | queryset.count() drops LIMIT/OFFSET information → queryset.count() does not work with sliced querysets |
---|
I left the doc patch separate, because I'm not sure we need to explicitly document this change in behaviour in the DB api - the old behaviour was never implied.
comment:13 by , 18 years ago
Summary: | queryset.count() does not work with sliced querysets → queryset.count() does not work correctly with sliced querysets |
---|
comment:14 by , 18 years ago
Patch needs improvement: | set |
---|---|
Triage Stage: | Ready for checkin → Accepted |
My earlier comment was that count() and limit/offset do not mix at the SQL level. You have worked around that by manually applying those parameters inside the Python code. I think this is counter-intuitive, since it's now no longer SQL-intuitive or Python-intuitive (slicing the single count() result changes the result?! We implemented limit/offset as slices because it was analogous -- you were slicing a sequence of results). But I can see the flip side, too, so I'm not going to veto this.
One problem with the patch is
>>> articles[8:100].count() -1
(using the same test data setup). You need to make line 224 be something like count = max(0, count - offset)
. Fix that and add a test and we're good to go, I think.
comment:15 by , 18 years ago
Maybe I'm not following, but you can't slice a count()
result as it does not return a QuerySet
.
It seems pretty Python-intuitive that len(articles[:5]) == articles[:5].count()
.
Thanks for picking up that bug, I'll fix now.
by , 18 years ago
Attachment: | queryset_count.3.patch added |
---|
comment:16 by , 18 years ago
Patch needs improvement: | unset |
---|---|
Triage Stage: | Accepted → Ready for checkin |
comment:17 by , 18 years ago
Resolution: | → fixed |
---|---|
Status: | new → closed |
comment:18 by , 16 years ago
Cc: | added |
---|
Hm.
We're definitely explicitly dropping the limit and offset if they're present, but there's no comment to explain why. When I get a little time, I'll try the test suite with the offending lines commented out (lines 183 and 184 in django/db/models/query.py).