在我的Rails应用程序中,我遇到了一个问题几次,我想知道其他人如何解决:
我有一些记录,其中值是可选的,所以有些记录有值,有些记录为空。
如果我按列排序,在某些数据库中空值优先排序,在某些数据库中空值最后排序。
例如,我的照片可能属于也可能不属于一个集合,即有些照片属于collection_id=nil
,有些照片属于collection_id=1
等。
如果我做Photo.order('collection_id desc)
然后在SQLite上我得到空最后,但在PostgreSQL上我得到空第一。
我不是SQL专家,但为什么不先按null排序,然后按你想要的排序呢?
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
如果您想要通用的东西(比如您在多个数据库中使用相同的查询),您可以使用(由@philT提供)
Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
尽管现在已经是2017年了,但对于NULL
s是否应该优先考虑,仍然没有达成共识。如果没有明确说明,您的结果将根据DBMS而变化。
标准没有指定null值与非null值的比较应该如何排序,除了任何两个null都被认为是相同的排序,并且null值应该在所有非null值的上面或下面排序。
来源,大多数dbms的比较
为了说明这个问题,我列出了Rails开发中最常见的几个案例:
PostgreSQL
NULL
s的值最高。
MySQL默认情况下,空值排序好像比任何非空值大。
来源:PostgreSQL文档
NULL
s的值最低。
当执行ORDER BY时,如果您执行ORDER BY…ASC和最后,如果你按…DESC .
source: MySQL documentation
SQLite
NULL
s的值最低。
<标题> 解决方案具有NULL值的行按升序高于具有正则值的行,按降序则相反。
来源
不幸的是,Rails本身还没有提供解决方案。
PostgreSQL特定
对于PostgreSQL,你可以很直观地使用:
Photo.order('collection_id DESC NULLS LAST') # NULLs come last
对于MySQL,你可以把负号放在前面,但是这个特性似乎没有文档。似乎不仅可以处理数值,还可以处理日期。
Photo.order('-collection_id DESC') # NULLs come last
PostgreSQL和MySQL特有的
为了同时覆盖它们,下面似乎可以工作:
Photo.order('collection_id IS NULL, collection_id DESC') # NULLs come last
但是,这个在SQLite中不起作用
<标题> 的通用解决方案为所有dbms提供交叉支持,您必须使用CASE
编写查询,@PhilIT已经建议:
Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
转换为首先按CASE
结果排序每个记录(默认升序,这意味着NULL
值将是最后一个),其次按calculation_id
.
Photo.order('collection_id DESC NULLS LAST')
我知道这是一个旧的,但我刚刚找到这个片段,它适用于我。
将负号放在column_name前面并反转顺序方向。它适用于mysql。更多细节
Product.order('something_date ASC') # NULLS came first
Product.order('-something_date DESC') # NULLS came last
有点晚了,但是有一种通用的SQL方法可以做到这一点。像往常一样,CASE
来拯救。
Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
Rails 6.1为PostgreSQL添加了nulls_first和nulls_last方法。
的例子:
User.order(User.arel_table[:login_count].desc.nulls_last)
来源:https://www.bigbinary.com/blog/rails-6-1-adds-nulls-first-and-nulls-last-to-arel
最简单的方法是使用:
.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问题(1,2,3)中引用的。我可以通过以下操作来解决这个问题:
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
唯一的缺点是必须为每个作用域执行此链接。
以下是一些Rails 6解决方案。
@Adam Sibik的回答是对不同数据库系统之间差异的一个很好的总结。
不幸的是,一些提出的解决方案,包括&;通用解决方案&;和"PostgreSQL和MySQL特定的",将不再与Rails 6 (ActiveRecord 6)工作,因为它改变了order()
的规范,不接受一些原始sql(我确认"PostgreSQL特定的"解决方案仍然可以在Rails 6.1.4中使用)。有关此更改的背景,请参见
Rails 6.1中SQL注入的更新贾斯汀。
为了避免这个问题,您可以像下面这样用Arel.sql
包装SQL语句,其中null在最后,提供您100%确定您给出的SQL语句是安全的。
Photo.order(Arel.sql('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id'))
仅供参考,如果您希望按照[TRUE, FALSE, NULL]的顺序对布尔列(例如is_ok
)进行排序,而不考虑数据库系统,这两种方式都应该工作:
Photo.order(Arel.sql('CASE WHEN is_ok IS NULL THEN 1 ELSE 0 END, is_ok DESC'))
Photo.order(Arel.sql('CASE WHEN is_ok IS NULL THEN 1 WHEN is_ok IS TRUE THEN -1 ELSE 0 END'))
(注意。)
在我的情况下,我需要通过ASC按开始和结束日期排序,但在少数情况下,end_date为空,行应该在上面,我使用
@invoice.invoice_lines.order('start_date ASC, end_date ASC NULLS FIRST')
将数组放在一起将保持顺序:
@nonull = Photo.where("collection_id is not null").order("collection_id desc")
@yesnull = Photo.where("collection_id is null")
@wanted = @nonull+@yesnull
http://www.ruby-doc.org/core/classes/Array.html#M000271
如果你想要跨数据库类型的一致结果,似乎你必须在Ruby中这样做,因为数据库本身解释null是在列表的前面还是后面。
Photo.all.sort {|a, b| a.collection_id.to_i <=> b.collection_id.to_i}