Rails:用空值sorting最后
在我的Rails应用程序中,我偶然遇到了一个问题,我想知道其他人是如何解决的:
我有某些logging的值是可选的,所以一些logging有一个值,有些为该列的空值。
如果我在某些数据库上按该列sorting,那么首先对这些零值进行sorting,然后在某些数据库上进行sorting。
例如,我有可能或可能不属于一个集合的照片,即有一些照片,其中collection_id=nil
和一些collection_id=1
等。
如果我做Photo.order('collection_id desc)
然后在SQLite上,我得到了空值,但在PostgreSQL上,我得到了空值。
有没有一个好的,标准的Rails方式来处理这个问题,并在任何数据库中获得一致的性能?
将数组添加到一起将保持顺序:
@nonull = Photo.where("collection_id is not null").order("collection_id desc") @yesnull = Photo.where("collection_id is null") @wanted = @nonull+@yesnull
我不是SQL的专家,但为什么不先sorting,如果什么东西是空的,然后按您想要如何sorting它。
Photo.order('collection_id IS NULL, collection_id DESC') # Null's last Photo.order('collection_id IS NOT NULL, collection_id DESC') # Null's first
如果你只使用PostgreSQL,你也可以这样做
Photo.order('collection_id DESC NULLS LAST') #Null's Last Photo.order('collection_id DESC NULLS FIRST') #Null's First
但SQLite3会给你错误。
在column_name前面加上减号 ,并颠倒顺序方向。 它在MySQL上工作。 更多细节
Product.order('something_date ASC') # NULLS came first Product.order('-something_date DESC') # NULLS came last
Photo.order('collection_id DESC NULLS LAST')
我知道这是一个旧的,但我只是发现这个片段,它适用于我。
表演晚了点,但有一个通用的SQL方式来做到这一点。 像往常一样, CASE
来救援。
Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
即使现在是2017年,对于NULL
是否应该优先考虑还没有达成共识。 如果没有你明确的说明,你的结果将会根据DBMS而变化。
该标准没有指定如何将NULL与非NULL值进行sorting,除了任何两个NULL被认为是相同的sorting,并且NULL应该sorting高于或低于所有非NULL值。
来源,比较大多数DBMSs
为了说明这个问题,我编写了一些关于Rails开发的最stream行的例子:
PostgreSQL的
NULL
s的值最高。
默认情况下,空值sorting就像大于任何非空值一样。
来源:PostgreSQL文档
MySQL的
NULL
s的值最低。
在执行ORDER BY时,如果执行ORDER BY … ASC,则首先显示NULL值,如果执行ORDER BY … DESC,则显示NULL值。
来源:MySQL文档
SQLite的
NULL
s的值最低。
具有NULL值的行高于具有常规值的行,并且按降序排列。
资源
解
不幸的是,Rails本身并没有提供解决scheme。
PostgreSQL的具体
对于PostgreSQL,你可以很直观地使用:
Photo.order('collection_id DESC NULLS LAST') # NULLs come last
MySQL的具体
对于MySQL,你可以预先设定减号,但这个function似乎没有logging。 似乎不仅用数值工作,而且用date工作。
Photo.order('-collection_id DESC') # NULLs come last
PostgreSQL和MySQL的具体
为了涵盖他们两个,这似乎工作:
Photo.order('collection_id IS NULL, collection_id DESC') # NULLs come last
不过,这个在SQLite中不起作用 。
通用解决scheme
为了为所有的DBMS提供交叉支持,你必须用@PhilITbuild议的CASE
来写一个查询:
Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
这意味着首先按CASE
结果sorting每个logging(默认升序,这意味着NULL
值将是最后一个),其次是calculation_id
。
最简单的方法是使用:
.order('name nulls first')
为了后人的缘故,我想强调一下与NULLS FIRST
有关的ActiveRecord错误。
如果您尝试拨打:
Model.scope_with_nulls_first.last
Rails将尝试调用reverse_order.first
,而reverse_order
与NULLS LAST
不兼容,因为它会尝试生成无效的SQL:
PG::SyntaxError: ERROR: syntax error at or near "DESC" LINE 1: ...dents" ORDER BY table_column DESC NULLS LAST DESC LIMIT...
这是几年前在一些尚未解决的Rails问题( 一 , 二 , 三 )中提到的。 我能够通过执行以下操作来解决此问题:
scope :nulls_first, -> { order("table_column IS NOT NULL") } scope :meaningfully_ordered, -> { nulls_first.order("table_column ASC") }
看起来,通过将这两个命令链接在一起,生成了有效的SQL:
Model Load (12.0ms) SELECT "models".* FROM "models" ORDER BY table_column IS NULL DESC, table_column ASC LIMIT 1
唯一的缺点是这个链接必须在每个范围内完成。
在我的情况下,我需要通过ASC的开始和结束datesorting行,但在一些情况下,end_date是空的,行应该在上面,我用
@invoice.invoice_lines.order('start_date ASC, end_date ASC NULLS FIRST')
如果你想要在数据库types中保持一致的结果,你似乎必须在Ruby中这样做,因为数据库本身解释了NULLS是否在列表的前面或末尾。
Photo.all.sort {|a, b| a.collection_id.to_i <=> b.collection_id.to_i}
但是这不是很有效率。