web-dev-qa-db-ja.com

Django:クエリに「NULLS LAST」を追加する

Postgresqlの「NULLS LAST」オプションを使用してモデルを並べ替えたいのですが。どうすればできますか?

私は何かを試しました

MyModel.objects.all().extra(order_by=('-price', 'NULLS LAST'))

しかし、私は得る

「キーワード「NULLS LAST」をフィールドに解決できません」

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

この機能はDjango 1.11に追加されました。

https://docs.djangoproject.com/en/dev/releases/1.11/

Nulls_firstおよびnulls_lastパラメーターをExpression.asc()およびdesc()に追加して、null値の順序を制御します。

74
kabucey

すべての列で透過的に行う場合は、SQL生成を再定義できます。これを行うには、カスタムコンパイラを使用するためにカスタムクエリを返すカスタムクエリセットを返す独自のマネージャが必要です。そのための私のコードはそのように見えます(Django 1.5):

from Django.db import models, connections

class NullsLastQuery(models.sql.query.Query):
    """
    Query that uses custom compiler,
    to utilize PostgreSQL feature of setting position of NULL records
    """
    def get_compiler(self, using=None, connection=None):
        if using is None and connection is None:
            raise ValueError("Need either using or connection")
        if using:
            connection = connections[using]

        # defining that class elsewhere results in import errors
        from Django.db.models.sql.compiler import SQLCompiler
        class NullsLastSQLCompiler(SQLCompiler):
            def get_ordering(self):
                result, group_by = super(NullsLastSQLCompiler, self
                    ).get_ordering()
                if self.connection.vendor == 'postgresql' and result:
                    result = [line + " NULLS LAST" for line in result]
                return result, group_by

        return NullsLastSQLCompiler(self, connection, using)

class NullsLastQuerySet(models.query.QuerySet):
    def __init__(self, model=None, query=None, using=None):
        super(NullsLastQuerySet, self).__init__(model, query, using)
        self.query = query or NullsLastQuery(self.model)

class NullsLastManager(models.Manager):
    def get_query_set(self):
        return NullsLastQuerySet(self.model, using=self._db)

class YourModel(models.Model):
    objects = NullsLastManager()
21
Tim Babych

私が見つけた最も近いことは、2つのステップでそれを行うことです。設定されたフィールドで最初に並べ替え、次にnullで並べ替えます。

this Gist(それ自体はこれら Django logs を介して):

all_projects = Project.objects.select_related().filter(
    company=company).order_by('-date_due')

q = all_projects.extra(select={'date_due_null': 'date_due is null'})
q = q.extra(order_by=['date_due_null'])
print q.query

幸運を。

19
Mariano

これはおそらく質問されたときには利用できませんでしたが、Django 1.8以降、これが最良の解決策だと思います:

from Django.db.models import Coalesce, Value
MyModel.objects.all().annotate(price_null=
    Coalesce('price', Value(-100000000)).order_by('-price_null')

Coalesceは最初のnull以外の値を選択するので、値price_nullを作成し、価格だけでnull-100000000に置き換えて並べ替えます(または+?)。

11
Mark

Django 1.9(そしておそらく1.8)の場合、これを使用できます:

from Django.db import connections, models
from Django.db.models.sql.compiler import SQLCompiler


class NullsLastSQLCompiler(SQLCompiler):
    def get_order_by(self):
        result = super().get_order_by()
        if result and self.connection.vendor == 'postgresql':
            return [(expr, (sql + ' NULLS LAST', params, is_ref))
                    for (expr, (sql, params, is_ref)) in result]
        return result


class NullsLastQuery(models.sql.query.Query):
    """Use a custom compiler to inject 'NULLS LAST' (for PostgreSQL)."""

    def get_compiler(self, using=None, connection=None):
        if using is None and connection is None:
            raise ValueError("Need either using or connection")
        if using:
            connection = connections[using]
        return NullsLastSQLCompiler(self, connection, using)


class NullsLastQuerySet(models.QuerySet):
    def __init__(self, model=None, query=None, using=None, hints=None):
        super().__init__(model, query, using, hints)
        self.query = query or NullsLastQuery(self.model)

そしてあなたのモデルで:

objects = NullsLastQuerySet.as_manager()

これは、ティムからの回答 https://stackoverflow.com/a/17077587/1569 に基づいています。

このサポートをDjangoに追加するためのチケットが再開されました: https://code.djangoproject.com/ticket/13312

10
blueyed

@ kabuceyの答え はDjango> = 1.11に最適ですが、少なくともDjango 1.8、1.9または1.10を使用している場合 https://www.isotoma.com/blog/2015/11/23/sorting-querysets-で説明されているように、カスタムFunc式を使用して「最後のNULL」動作を実現できます。 with-nulls-in-Django /

from Django.db.models import Func

class IsNull(Func):
    template = '%(expressions)s IS NULL'

MyModel.objects.all().annotate(
    price_isnull=IsNull('price_isnull'),
    ).order_by(
        'price_isnull',
        '-price',
        )

最初のorder_by引数は、リストをprice_isnullで昇順に並べ替え、True > False以降のnull価格のアイテムをリストの最後に強制します。

2
Edward D'Souza

Django <v1.11 with Django v1.11 styleにマネージnull機能を追加する別の方法があります:

from my_project.utils.Django import F
MyModel.objects.all().order_by(F('price').desc(nulls_last=True))
# or
MyModel.objects.all().order_by(F('price').desc().nullslast())

短所:

  1. Django 1.11への簡単な移行
  2. クエリコンパイラの内部については詳しく説明しません

そのためには、Django.db.models.FおよびDjango.db.models.expressions.OrderByクラスをオーバーライドする必要があります。

from Django.db.models import F as DjangoF
from Django.db.models.expression import OrderBy as DjangoOrderBy


class OrderBy(DjangoOrderBy):
    def __init__(self, expression, descending=False, nulls_last=None):
        super(OrderBy, self).__init__(expression, descending)
        self.nulls_last = nulls_last
    ...

    def as_sql(self, compiler, connection, template=None, **extra_context):
        ...
        ordering_value = 'DESC' if self.descending else 'ASC'
        if self.nulls_last is not None:
            nulls_value = 'LAST' if self.nulls_last else 'FIRST'
            ordering_value += ' NULLS ' + nulls_value

        placeholders = {
            'expression': expression_sql,
            'ordering': ordering_value,
        }
        ...

    def nullslast(self):
        self.nulls_last = True

    def nullsfirst(self):
        self.nulls_last = False


class F(DjangoF):
    ...

    def asc(self, nulls_last=None):
        return OrderBy(self, nulls_last=nulls_last)

    def desc(self, nulls_last=None):
        return OrderBy(self, descending=True, nulls_last=nulls_last)
1
stdword