Django:添加“NULLS LAST”进行查询

时间:2013-02-27 19:52:44

标签: django

我想使用Postgresql的“NULLS LAST”选项对模型进行排序。 怎么可能呢?

我试过像

这样的东西

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

但是我得到了

“无法将关键字'NULLS LAST'解析为字段”

8 个答案:

答案 0 :(得分:59)

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/

  

在Expression.asc()中添加了nulls_first和nulls_last参数   和desc()来控制空值的排序。

答案 1 :(得分:21)

如果希望透明地在所有列上完成,则可以重新定义sql生成。为此,您需要拥有自己的Manager来返回自定义QuerySet以返回自定义Query以使用自定义编译器。我的代码看起来像那样(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()

答案 2 :(得分:19)

我发现的最近的事情是分两步完成。首先在填充字段上排序,然后在空值上排序:

通过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
祝你好运。

答案 3 :(得分:11)

当问到这个问题时,这可能不可用,但是自从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选择第一个非空值,因此您可以创建一个值price_null来订购,只是价格但null替换为-100000000(或{ {1}}?)。

答案 4 :(得分:10)

对于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/15690中的回答。

重新打开了向Django添加支持的票证:https://code.djangoproject.com/ticket/13312

答案 5 :(得分:1)

@kabucey's answer最适合Django> = 1.11,但如果您至少使用Django 1.8,1.9或1.10,则可以使用自定义Func表达式来实现&#34 ; NULLS最后"行为,如https://www.isotoma.com/blog/2015/11/23/sorting-querysets-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以来的列表末尾。

答案 6 :(得分:1)

还有另一种向Django添加托管空值功能的方法< v1.11与Django v1.11风格:

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. 我们没有深入研究查询编译器内部
  3. 为此,我们需要覆盖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)
    

答案 7 :(得分:1)

我们想按语句,多个ASC和一些DESC都用NULLS LAST链接多个顺序。 order_by似乎不存在这种可能性,因为它具有以下调用:

obj.query.clear_ordering(force_empty=False)

因此,您可以通过添加add_ordering调用来执行以下操作:

qs = ATeamModel.objects.filter(whatever=1)
qs.query.add_ordering(F('date_updated').desc(nulls_last=True))
qs.query.add_ordering(F('date_created').desc(nulls_last=True))

qs...