Opened 7 years ago

Closed 7 years ago

Last modified 7 years ago

#28657 closed Bug (invalid)

Strangely odd behaviour when ordering QuerySet by BooleanField.

Reported by: Leonardo Arroyo Owned by: nobody
Component: Database layer (models, ORM) Version: 1.11
Severity: Normal Keywords: queryset, boolean, order, ordering
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Leonardo Arroyo)

I have recently encountered problems with duplicated results in pagination and trying to pin down the problem this seems to be a problem in the Django queryset. I'll attempt to explain the problem. Take a look at the following Queryset:

>>> p = Project.objects.all().order_by("closed")
>>> p
<QuerySet [<Project: Campanha de Incentivo - Alimentando a Solidariedade (Outubro)>, <Project: Campanha de Incentivo - Projeto Fora das 4 Paredes>, <Project: projeto luciele>, <Project: Campanha de Incentivo - Menos é Mais>, <Project: Campanha de Incentivo - Projeto Conviver>, <Project: Campanha de Incentivo - Aula de Música>, <Project: RPINCLUSÃO - Informações, Acessibilidade e Inclusão>, <Project: Ong ong>, <Project: Campanha de Incentivo - Projeto Alimentando Vidas>, <Project: Bola na Rede>, <Project: Campanha de Incentivo - Projeto Bolas de Pelos>, <Project: Mãos Solidárias>, <Project: Campanha de Incentivo - GRUPO DE ATITUDE SOCIAL - Moradores de Rua>, <Project: APAE pede Socorro>, <Project: Campanha de Incentivo - Anjos da Madrugada>, <Project: Pintura de Casas Emergenciais com o TETO - São Paulo>, <Project: Campanha de Incentivo - Aos Olhos do Pai>, <Project: Legião da Boa Vontade>, <Project: Campanha de Incentivo - Você é a gota que faltava, Doe Sangue!>, <Project: Campanha de Incentivo - Sonhar com Esperança>, '...(remaining elements truncated)...']>

The 'closed' field here is a Boolean. Everything seems to be correct except for this:

>>> p[0]                                                   
<Project: Campanha de Incentivo - Anjos da Madrugada>
>>> p[1]
<Project: Bola na Rede>

When I get items from the queryset, they don't match the queryset repr order. What is even weirder is that neither the repr order nor the getitem order matches the PostgreSQL query.

>>> print(p.query)
SELECT "ovp_projects_project"."id", "ovp_projects_project"."image_id", "ovp_projects_project"."address_id", "ovp_projects_project"."owner_id", "ovp_projects_project"."organization_id", "ovp_projects_project"."name", "ovp_projects_project"."slug", "ovp_projects_project"."published", "ovp_projects_project"."highlighted", "ovp_projects_project"."applied_count", "ovp_projects_project"."max_applies", "ovp_projects_project"."max_applies_from_roles", "ovp_projects_project"."public_project", "ovp_projects_project"."minimum_age", "ovp_projects_project"."hidden_address", "ovp_projects_project"."crowdfunding", "ovp_projects_project"."published_date", "ovp_projects_project"."closed", "ovp_projects_project"."closed_date", "ovp_projects_project"."deleted", "ovp_projects_project"."deleted_date", "ovp_projects_project"."created_date", "ovp_projects_project"."modified_date", "ovp_projects_project"."details", "ovp_projects_project"."description" FROM "ovp_projects_project" ORDER BY "ovp_projects_project"."closed" ASC

When hitting the database with such query, only removing a couple of fields to make it easier to read, this is what I get:
https://code.djangoproject.com/attachment/ticket/28657/django-boolean-bug.png

These are 3 different orders. Now, there's one little thing I can do to fix the QuerySet, which is iterating over it.

>>> for x in p:
...   print(x.pk)
... 
81
74
75
91
83
61
(...)

Suddenly the queryset now matches the result from my PostgreSQL query and everything is now correct.

>>> p
<QuerySet [<Project: Pintura de Casas Emergenciais com o TETO - São Paulo>, <Project: Campanha de Incentivo - Anjos da Madrugada>, <Project: Ong ong>, <Project: projeto luciele>, <Project: Campanha de Incentivo - Projeto Alimentando Vidas>, <Project: Campanha de Incentivo - Menos é Mais>, <Project: Campanha de Incentivo - Alimentando a Solidariedade (Outubro)>, <Project: Campanha de Incentivo - Aula de Música>, <Project: Campanha de Incentivo - Projeto Fora das 4 Paredes>, <Project: Campanha
de Incentivo - Padoca Artesanal>, <Project: Campanha de Incentivo - EmpoderA>, <Project: Campanha de Incentivo - Moto Sopa>, <Project: Campanha Incentivo - Projeto Vale Jequitinhonha>, <Project: Campanha de Incentivo - Eternas Crianças>, <Project: Campanha de Incentivo - Festa de Natal>, <Project: Treinador voluntário de futebol - Águias Livres>, <Project: Fundação Gol de Letra - Faça parte deste time!>, <Project: Campanha de Incentivo - Ganhando Almas>, <Project: Campanha de
Incentivo - Acolchoados de Ponto Cruz e Patchwork>, <Project: Campanha de Incentivo - Show de Encantamento Solidário>, '...(remaining elements truncated)...']>
>>> p[0]
<Project: Pintura de Casas Emergenciais com o TETO - São Paulo>
>>> p[1]
<Project: Campanha de Incentivo - Anjos da Madrugada>

I'm trying to find out where the problem is really happening but I have limited knowledge about django ORM internals, so I haven't gotten that far yet. Currently I'm hacking around it by iterating over the queryset before paginating it and passing it to my view.

Things get even weirder if I try to order by 2 boolean fields, with issues such as:

>>> p = Project.objects.all().order_by("closed", "-highlighted")                                                       
>>> p                                                      
<QuerySet [<Project: Campanha Incentivo - Projeto Vale Jequitinhonha>, <Project: Campanha de Incentivo - Alimentando a Solidariedade (Outubro)>, <Project: Campanha de Incentivo - Festa do Dia Das Crianças>, <Project: Campanha de Incentivo - Cartas para o "Papai Noel">, <Project: Pintura de Casas Emergenciais com o TETO - São Paulo>, <Project: Campanha de Incentivo - Padoca Artesanal>, <Project: Campanha de Incentivo - EmpoderA>, <Project: Construção de Casas Emergenciais com o TETO - São Paulo - Estrutura e entrega  da casa>, <Project: Campanha de Incentivo - Doação de Brinquedos>, <Project: Campanha de Incentivo - Pequena ajuda, grande transformação>, <Project: Campanha de Incentivo - Aula de Música>, <Project: Campanha de Incentivo - Aos Olhos do Pai>, <Project: APAE pede Socorro>, <Project: Campanha de Incentivo - GRUPO DE ATITUDE SOCIAL - Moradores de Rua>, <Project: Mãos Solidárias>, <Project: Campanha de Incentivo - Projeto Bolas de Pelos>, <Project: Campanha de Incentivo - Projeto Conviver>, <Project: Bola na Rede>, <Project: Campanha de Incentivo - Você é a gota que faltava, Doe Sangue!>, <Project: projeto luciele>, '...(remaining elements truncated)...']>
>>> p[0], p[0].pk
(<Project: Pintura de Casas Emergenciais com o TETO - São Paulo>, 81)
>>> p[1], p[1].pk
(<Project: Pintura de Casas Emergenciais com o TETO - São Paulo>, 81)

Attachments (1)

django-boolean-bug.png (40.3 KB ) - added by Leonardo Arroyo 7 years ago.

Download all attachments as: .zip

Change History (4)

by Leonardo Arroyo, 7 years ago

Attachment: django-boolean-bug.png added

comment:1 by Leonardo Arroyo, 7 years ago

Description: modified (diff)

comment:2 by Tim Graham, 7 years ago

Resolution: invalid
Status: newclosed

My hunch is that PostgreSQL is "at fault", not Django. When you execute order_by() with a boolean field, the results with the same value (True or False) are ordered randomly. If you want a deterministic order, you could do something like .order_by('closed', 'pk').

In the future, please see TicketClosingReasons/UseSupportChannels to help confirm a bug before creating a ticket.

comment:3 by Leonardo Arroyo, 7 years ago

This was indeed the problem. For some reason I assumed ordering by a boolean field would yield consistent results when LIMITing it on PostgreSQL. It does not. Ordering by pk + boolean fixes it.

Thanks.

Note: See TracTickets for help on using tickets.
Back to Top