Rails:最后以空值排序

时间:2011-04-28 23:24:07

标签: ruby-on-rails ruby-on-rails-3 sorting activerecord null

在我的Rails应用程序中,我遇到过几次问题,我想知道其他人如何解决:

我有某些记录,其中值是可选的,因此某些记录具有值,而某些记录的值为null。

如果我在某些数据库上按该列排序,则首先对空值进行排序,在某些数据库上对空值进行排序。

例如,我有可能属于或可能不属于集合的照片,即有些照片中collection_id=nil和某些collection_id=1等等。

如果我执行Photo.order('collection_id desc)那么在SQLite上我得到空值,但在PostgreSQL上我首先获得空值。

是否有一种很好的标准Rails方法来处理这个并在任何数据库中获得一致的性能?

12 个答案:

答案 0 :(得分:231)

我不是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')

答案 1 :(得分:31)

即使现在是2017年,关于NULL s应该优先还是尚未达成共识。如果没有明确说明,您的结果将根据DBMS而有所不同。

  

标准没有指定与非NULL值相比应该如何排序NULL,除了任何两个NULL被认为是等同排序的,并且NULL应该在所有非NULL值之上或之下排序

     

source, comparison of most DBMSs

为了说明这个问题,我在Rails开发中编制了一些最常见的案例列表:

的PostgreSQL

NULL具有最高价值。

  

默认情况下,null值的排序方式大于任何非空值。

     

source: PostgreSQL documentation

的MySQL

NULL的值最低。

  

在执行ORDER BY时,如果您执行ORDER BY ... ASC,则首先显示NULL值,如果执行ORDER BY ... DESC,则显示最后一个。

     

source: MySQL documentation

SQLite的

NULL的值最低。

  

具有NULL值的行高于具有常规值的行按升序排列,并且按降序顺序颠倒。

     

source

解决方案

不幸的是,Rails本身并没有为它提供解决方案。

PostgreSQL特定

对于PostgreSQL,您可以非常直观地使用:

Photo.order('collection_id DESC NULLS LAST') # NULLs come last

MySQL特定

对于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。< / p>

答案 2 :(得分:12)

减号放在column_name前面并反转订单方向。它适用于mysql。 More details

Product.order('something_date ASC') # NULLS came first
Product.order('-something_date DESC') # NULLS came last

答案 3 :(得分:11)

Photo.order('collection_id DESC NULLS LAST')

我知道这是一个旧的,但我刚刚发现这个片段,它对我有用。

答案 4 :(得分:7)

该节目迟到但有一种通用的SQL方法可以做到这一点。像往常一样,CASE来救援。

Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')

答案 5 :(得分:6)

出于后人的考虑,我想强调与NULLS FIRST相关的ActiveRecord错误。

如果您尝试致电:

Model.scope_with_nulls_first.last

Rails会尝试调用reverse_order.first,而reverse_orderNULLS LAST不兼容,因为它会尝试生成无效的SQL:

PG::SyntaxError: ERROR:  syntax error at or near "DESC"
LINE 1: ...dents"  ORDER BY table_column DESC NULLS LAST DESC LIMIT...

几年前在一些仍未解决的Rails问题(onetwothree)中引用了这一点。我能够通过以下方式解决这个问题:

  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

唯一的缺点是必须为每个范围进行此链接。

答案 6 :(得分:5)

最简单的方法是使用:

.order('name nulls first')

答案 7 :(得分:3)

一起添加数组将保留顺序:

@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

答案 8 :(得分:1)

在我的情况下,我需要按ASC的开始和结束日期对行进行排序,但在极少数情况下,end_date为null并且行应该在上面,我使用

browser.wait()

答案 9 :(得分:1)

这里有一些 Rails 6 解决方案。

@Adam Sibik 的

The answer 很好地总结了各种数据库系统之间的差异。

不幸的是,一些提出的解决方案,包括“通用解决方案”和“PostgreSQL 和 MySQL 特定的”,将不再适用于 Rails 6 (ActiveRecord 6) 由于其更改的 order() 规范不接受某些原始 SQL(我确认“PostgreSQL 特定”解决方案从 Rails 6.1.4 开始仍然有效)。有关此更改的背景,请参见例如
贾斯汀的“Updates for SQL Injection in Rails 6.1”。

为了规避这个问题,您可以用 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'))

(n.b.,SQLite 没有 Boolean 类型,因此前者可能更安全,尽管这无关紧要,因为 Rails 应该保证该值是 0 或 1(或 NULL)。)

答案 10 :(得分:-1)

首先获取collection_id不为空且按collection_id降序排列的照片。

然后获取collection_id为空的照片并添加它们。

Photos.where.not(collection_id: [nil, ""])
  .order(collection_id: :desc) + where(collection_id: [nil, ""])

答案 11 :(得分:-3)

如果您希望跨数据库类型获得一致的结果,那么您似乎必须在Ruby中执行此操作,因为数据库本身会解释NULLS是否位于列表的前面或末尾。

Photo.all.sort {|a, b| a.collection_id.to_i <=> b.collection_id.to_i}

但这不是很有效。