Django: упорядочить по позиции, игнорируя NULL - PullRequest
39 голосов
/ 08 марта 2011

У меня проблема с упорядочением набора запросов django.

Моя модель содержит поле с именем position (a PositiveSmallIntegerField ), которое я хотел бы использовать для упорядочения результатов запроса.

Я использую order_by('position'), который прекрасно работает.

Проблема : мое поле position имеет значение NULL (null=True, blank=True), потому что я не хочу указывать позицию для каждых 50000 экземпляров моей модели: (

Когда некоторые экземпляры имеют нулевую «позицию», order_by возвращает их в начало списка: я бы хотел, чтобы они были в конце ...

В RAW SQL я писал такие вещи, как "IF(position IS NULL or position='', 1, 0)" (см. http://www.shawnolson.net/a/730/mysql-sort-order-with-null.html): возможно ли получить тот же результат, используя Django, без написания необработанного SQL?

Большое спасибо!

Ответы [ 6 ]

57 голосов
/ 08 марта 2011

Вы можете использовать аннотацию () из соглашение django , чтобы сделать трюк:

items = Item.objects.all().annotate(null_position=Count('position')).order_by('-null_position', 'position')
19 голосов
/ 11 мая 2015

Начиная с Django 1.8, вы можете использовать Coalesce() для преобразования NULL в 0.

Пример:

import datetime    
from django.db.models.functions import Coalesce, Value

from app import models


# Coalesce works by taking the first non-null value.  So we give it
# a date far before any non-null values of last_active.  Then it will
# naturally sort behind instances of Box with a non-null last_active value.

the_past = datetime.datetime.now() - datetime.timedelta(days=10*365)
boxes = models.Box.objects.all().annotate(
    new_last_active=Coalesce(
        'last_active', Value(the_past)
    )
).order_by('-new_last_active')
9 голосов
/ 31 августа 2013

Использование extra (), как сказал Игнасио, значительно оптимизирует конечный запрос.В моей заявке я сэкономил более 500 мс (это много для запроса) при обработке базы данных с использованием extra () вместо annotate ()

Вот как это будет выглядеть в вашем случае:

items = Item.objects.all().extra(
    'select': {
        'null_position': 'CASE WHEN {tablename}.position IS NULL THEN 0 ELSE 1 END'
     }
).order_by('-null_position', 'position')

{имя_таблицы} должно быть примерно таким: {Приложение элемента} _item после имени таблиц по умолчанию в django.

5 голосов
/ 17 мая 2018

Обидно, что на SO так много вопросов, которые не помечены как дубликаты. Смотрите (например) этот ответ для нативного решения для Django 1.11 и новее. Вот краткая выдержка:

Добавлены параметры nulls_first и nulls_last в Expression.asc () и desc () для управления порядком нулевых значений.

Пример использования (из комментария к этому ответу):

from django.db.models import F 
MyModel.objects.all().order_by(F('price').desc(nulls_last=True))

Кредит отправляется первоначальному ответу автора и комментатора.

3 голосов
/ 18 февраля 2015

Я обнаружил, что синтаксис в ответе Пабло необходимо обновить до следующего при моей установке 1.7.1:

items = Item.objects.all().extra(select={'null_position': 'CASE WHEN {name of Item's table}.position IS NULL THEN 0 ELSE 1 END'}).order_by('-null_position', 'position')
1 голос
/ 08 марта 2011

QuerySet.extra() может использоваться для вставки выражений в запрос и упорядочения по ним.

...