Django:向查询添加“ NULLS LAST”


76

我想通过使用Postgresql的“ NULLS LAST”选项对模型进行排序。怎么做?

我尝试了类似的东西

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

但是我明白了

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

Answers:


131
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()和desc()中添加了nulls_first和nulls_last参数,以控制null值的顺序。


2
有没有办法将此添加到Meta模型的类?还是我们使用领域的F对象做同样的事情?orderingMeta
Erdin Eray

结果查询有额外的开销吗?我想在传递订购字段的通用方法中使用它。有些字段不是空字段,因此它们不是必需的。有些字段也为空。对于非null的字段,是否可以将其添加到查询中呢?
Sandeep Balagopal

当您想对元素进行排序并使用排序查询(例如在“ for”中)时,这非常有用。我发现在first()/ last()中使用描述的顺序存在问题。它们都返回查询的第一个元素。
MarcinEl

22

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

6
这有点邪恶,但辉煌。
Wilfred Hughes

21

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

通过这个要点(本身通过这些django日志):

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

注意:请注意有关的警告extra(),并且将来可能会不推荐使用警告


2
别客气。考虑到Malcolm Treddinick的这个Google网上论坛帖子,我认为没有完美的解决方案,或者Django直接考虑的解决方案。
马里亚诺

12

提出问题时,这可能不可用,但是自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(或+?)代替。


实际上,这是一个不错的技巧,因为它不需要创建NULLS LAST索引,也不需要此类事情来
加快

1
@valentjedi这是否不需要带有表达式索引Coalesce('price', Value(-100000000)?这与创建NULLS LAST索引相同。在这里dba.stackexchange.com/a/99009有人说的和我说的一样
jperelli

@jperelli您实际上是正确的。但就我而言,这种破解比添加NULLS LAST更快,因此它令我满意。
valignatev

这对我有用。我将其与django 1.11的
Subquery

1
该值在django.db.models.functions模块中不可用。而是在django.db.models模块中可用。
sajid

11

对于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()

这基于Tim在https://stackoverflow.com/a/17077587/15690中的回答。

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


您是否对如何包括将空字符串放在末尾的功能有任何想法?
邓肯

2
@Duncan目前无法不付款,对不起..;)
染上了2016年

@blueyed,没问题。我能得到它的工作很好地利用与案例相结合注释和当表达
邓肯

3

@kabucey的答案最适合Django> = 1.11,但如果至少使用Django 1.8、1.9或1.10,则可以使用自定义Func表达式来实现“ NULLS Last”行为,如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


2

还有另一种使用Django v1.11样式将托管null功能添加到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. 我们不深入查询查询编译器内部

为此,我们需要重写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

我们想通过语句,多个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...
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.