我想通过使用Postgresql的“ NULLS LAST”选项对模型进行排序。怎么做?
我尝试了类似的东西
MyModel.objects.all().extra(order_by=('-price', 'NULLS LAST'))
但是我明白了
“无法将关键字'NULLS LAST'解析为字段”
Answers:
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值的顺序。
如果希望透明地在所有列上完成此操作,则可以重新定义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()
我发现最近的事情是分两个步骤进行。首先在填充字段上排序,然后在空值上排序:
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()
,并且将来可能会不推荐使用该警告。
提出问题时,这可能不可用,但是自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
(或+
?)代替。
Coalesce('price', Value(-100000000)
?这与创建NULLS LAST索引相同。在这里dba.stackexchange.com/a/99009有人说的和我说的一样
对于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。
@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
。
还有另一种使用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())
缺点:
为此,我们需要重写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)
我们想通过语句,多个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...
Meta
模型的类?还是我们使用领域的F
对象做同样的事情?ordering
Meta