Add to QuerySet method `one` that acts as `first` but without ordering

88 views
Skip to first unread message

alTus

unread,
Apr 13, 2019, 11:48:29 AM4/13/19
to Django developers (Contributions to Django itself)
Hello.

Please consider if that proposal can be useful not only for me :)

`QuerySet.first` is quite useful shortcut but its drawback is that it involves ordering so some DB queries become expensive.
But quite often (and very often while debugging) there's a need just to get one object that satisfy some filters.

Current `first` implementation to compare with:

    def first(self):
        """Return the first object of a query or None if no match is found."""
        for obj in (self if self.ordered else self.order_by('pk'))[:1]:
            return obj

Proposal (as an example of implementation):

    def one(self):
        """Return one random object of a query or None if no match is found."""
        for obj in self.order_by()[:1]:
            return obj

That would be short, simple and wouldn't require any imports in client code.

Thank you.

Adam Johnson

unread,
Apr 13, 2019, 2:24:30 PM4/13/19
to django-d...@googlegroups.com
Doesn’t it work to do qs[0] ?

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/c4337df6-c0f3-44fe-bbc0-01fe01cdf621%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
--
Adam

alTus

unread,
Apr 13, 2019, 2:40:40 PM4/13/19
to Django developers (Contributions to Django itself)
No, because qs[0] will raise exception if nothing found (and still it will have ordering).

суббота, 13 апреля 2019 г., 21:24:30 UTC+3 пользователь Adam Johnson написал:
Doesn’t it work to do qs[0] ?
On Sat, 13 Apr 2019 at 17:48, alTus <mort...@gmail.com> wrote:
Hello.

Please consider if that proposal can be useful not only for me :)

`QuerySet.first` is quite useful shortcut but its drawback is that it involves ordering so some DB queries become expensive.
But quite often (and very often while debugging) there's a need just to get one object that satisfy some filters.

Current `first` implementation to compare with:

    def first(self):
        """Return the first object of a query or None if no match is found."""
        for obj in (self if self.ordered else self.order_by('pk'))[:1]:
            return obj

Proposal (as an example of implementation):

    def one(self):
        """Return one random object of a query or None if no match is found."""
        for obj in self.order_by()[:1]:
            return obj

That would be short, simple and wouldn't require any imports in client code.

Thank you.


--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-d...@googlegroups.com.
--
Adam

Florian Apolloner

unread,
Apr 13, 2019, 3:53:52 PM4/13/19
to Django developers (Contributions to Django itself)
qs.order_by().first() should do what you want and is not worth adding .one()

Cheers,
Florian

alTus

unread,
Apr 13, 2019, 4:58:24 PM4/13/19
to Django developers (Contributions to Django itself)
I've posted the source code if `first`. You can see there that if qs is not ordered then ordering by pk is added.
It's the main point of this issue btw.

суббота, 13 апреля 2019 г., 22:53:52 UTC+3 пользователь Florian Apolloner написал:

Florian Apolloner

unread,
Apr 13, 2019, 5:04:25 PM4/13/19
to Django developers (Contributions to Django itself)
On Saturday, April 13, 2019 at 10:58:24 PM UTC+2, alTus wrote:
I've posted the source code if `first`. You can see there that if qs is not ordered then ordering by pk is added.
It's the main point of this issue btw.

Oh I didn't realize that first enforces ordering. Yet another function is not a good idea though I fear. I'd check what the reason for enforcing ordering in `.first` are and maybe we can just drop that. qs[0] wouldn't enforce ordering either (or does it), so why would first do that…

Tom Forbes

unread,
Apr 13, 2019, 5:15:07 PM4/13/19
to django-d...@googlegroups.com
I don’t think we should add a method like this for a few reasons. Firstly without an order by in SQL the order of rows is undefined, in practice myql orders rows but Postgres returns a different order per transaction. This will be confusing to users who don’t understand this and come to implicitly rely on first() being stable.

Secondly if you are filtering on an indexed column the overhead will be next to none. Is this not the case?

And lastly, changing this would be major backwards incompatible change.

Tom
--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.

To post to this group, send email to django-d...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.

Joshua Lawes

unread,
Apr 13, 2019, 7:13:52 PM4/13/19
to django-d...@googlegroups.com
If you just want one object that matches your filters during debugging, without overhead, why don’t you use .last() instead?

Илья

unread,
Apr 14, 2019, 3:07:40 AM4/14/19
to django-d...@googlegroups.com
First/last enforces ordering because of its nature and also we can't break compatibility. And second factor is that qs[0] as I wrote above can raise exception and also will still have ordering (from model.meta for example). 

вс, 14 апр. 2019 г., 0:04 Florian Apolloner <f.apo...@gmail.com>:
--
You received this message because you are subscribed to a topic in the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-developers/azvOPaEElOY/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-develop...@googlegroups.com.

To post to this group, send email to django-d...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.

Shai Berger

unread,
Apr 14, 2019, 4:23:17 AM4/14/19
to django-d...@googlegroups.com
Hi,

I agree that first() should imply ordering, and bemoan the decision,
made several years ago, that first() should be the answer to all the
people asking for get_or_none().

That said, get_or_none()'s definition is trivial:

def get_or_none(qs. *args, **kw):
try:
return qs.get(*args, **kw)
except ObjectDoesNotExist:
return None

and it's not hard to add it as a method on your own querysets or even
monkeypatch it into Django's using a descriptor:

class MonkeyPatcher:
def __init__(self, method):
self.method = method
def __get__(self, instance, cls):
return functools.partial(self.method, instance)

QuerySet.get_or_none = MonkeyPatcher(get_or_none)

(all above untested, Caveat Lector).

On a side note, you seem to be very worried about the possibility that
an exception will be raised. It may be an issue in terms of flow
control, but not in terms of overhead, so including code to handle an
exception does solve that issue.

HTH,
Shai.

alTus

unread,
Apr 14, 2019, 5:13:54 AM4/14/19
to Django developers (Contributions to Django itself)
Hi. Thank you for that detail response.

It needs to be clarified that I'm not that worried about exception as it could seem :)
It was just an answer to those 2 comments to show that their workarounds result in different behaviour than proposed `one`.

`get_or_none` would be useful in my opinion too but still it does not exactly the same thing because it would still raise MultipleObjectsReturned.

On the other hand, `one` is:
1) totally safe: None for nothing, same as `get` for 1 object, some random row if multiple found
2) have the same DB perfomance as `get`
3) has no overhead for exception (can affect performance in long cycles)


воскресенье, 14 апреля 2019 г., 11:23:17 UTC+3 пользователь Shai Berger написал:

Florian Apolloner

unread,
Apr 14, 2019, 6:02:57 AM4/14/19
to Django developers (Contributions to Django itself)
Hi,

while your arguments for adding `.one` are all good and well, I think adding yet another function will add quite some confusion and will make arguing harder imo. As a middleground I could imagine adding an `ordered=True/False` argument to first to turn off ordering as needed. This will also make it easier to argue about it in the docs I think. Would that work?

Cheers,
Florian

Curtis Maloney

unread,
Apr 14, 2019, 8:30:33 AM4/14/19
to django-d...@googlegroups.com
I was thinking the same for most of this thread... I would just
bike-shed it as, for instance "ensure_ordering=False" or
"stable_order=False" so you more clearly know what you're asking for.

--
Curtis

Илья

unread,
Apr 14, 2019, 9:51:55 AM4/14/19
to django-d...@googlegroups.com
From my subjective point of view `one` is more short and concise, but your points are valid too, so something like `qs.first(ordered=False)` seems to be a reasonable tradeoff.

I'd say that its arg should be keyword only to be explicit. 

And this arg doesn't then need to be added to `last`. 

вс, 14 апр. 2019 г., 13:03 Florian Apolloner <f.apo...@gmail.com>:
--
You received this message because you are subscribed to a topic in the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-developers/azvOPaEElOY/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
Reply all
Reply to author
Forward
0 new messages