Rails:空值顺序最后


83

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

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

如果按该列在某些数据库上排序,则空值将首先排序,而在某些数据库上,空数将最后排序。

例如,我有可能属于或不属于某个收藏集的照片,即有些照片在哪里collection_id=nil,有些在哪里collection_id=1等等。

如果这样做的Photo.order('collection_id desc)话,那么在SQLite上我最后得到的是空值,而在PostgreSQL上我首先得到的是空值。

有没有一种很好的,标准的Rails方法来处理此问题并在任何数据库中获得一致的性能?

Answers:


0

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

@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


2
好吧,我不喜欢这个主意,但是我认为这会起作用。抱歉让它打开了这么长时间,我希望还会有其他答案。尽管花了一些时间对此进行了思考,但我认为可以将其作为Photo模型的一种方法,然后再感觉也不会太糟。
安德鲁

如果您使用的是mysql,这很容易。请参阅我的解决方案。
雅各布

是的,我应该提到我的只是我所能找到的最不可知的方式。
埃里克(Eric)

8
这是一个坏主意,因为where它不返回数组,而是返回一个ActiveRecord :: Relation并将结果强制进入数组将导致所有期望标准ActiveRecord :: Relation失败的事件(例如分页)。
Mike Bethany 2014年

1
是的,尽管看起来可用的方法要么突破了AR,要么(不完全)可移植。
埃里克

269

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

21
+1比接受的答案好得多,可以通过Arel
m_x

1
哇,这是一个老评论!所以我想我想说的是:p = Photo.arel_table; Photo.order(p[:collection_id].eq(nil)).order(p[:collection_id].desc)
m_x 2014年

2
请注意,从Rails 4.2开始,NULLS LAST不允许您链接.last()到查询。我在下面发布了解决方法。
Lanny Bose 2015年

1
为什么排序时IS NULL将NULL行放在最后?您会认为它将把它们放在第一位。
jackocnr

2
@jackocr myguess:如果为true,则IS NULL的结果为1,如果为false,则为0,已排序,0在1之前...
Intentss

36

即使现在是2017年,关于NULLs是否应优先考虑仍未达成共识。如果没有明确说明,结果将取决于DBMS。

该标准没有指定与非NULL值相比应如何对NULL进行排序,只是将任何两个NULL视为均等排序,并且NULL应该在所有非NULL值之上或之下排序。

资料,大多数DBMS的比较

为了说明这个问题,我列出了一些有关Rails开发的最受欢迎的案例:

PostgreSQL的

NULL的值最高。

默认情况下,空值的排序似乎大于任何非空值。

来源:PostgreSQL文档

的MySQL

NULL的值最低。

在执行ORDER BY时,如果执行ORDER BY ... ASC,则将首先显示NULL值;如果执行ORDER BY ... DESC,则将显示NULL值。

来源:MySQL文档

SQLite的

NULL的值最低。

具有NULL值的行以升序高于具有常规值的行,并且以降序相反。

资源

不幸的是,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


14
Photo.order('collection_id DESC NULLS LAST')

我知道这是一个旧的代码,但我刚刚找到了该代码段,它对我有用。


我不知道它在哪个版本的Ruby / Rails中起作用,但是对于ruby 2.5和rails 5似乎不起作用。
Tasos Anesiadis

13

在column_name前面放置减号,并反转顺序方向。它适用于mysql。更多细节

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

10

演出有点晚,但是有一种通用的SQL方法可以做到。和往常一样,CASE进行救援。

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


6

为了后代的缘故,我想突出显示与相关的ActiveRecord错误NULLS FIRST

如果您尝试致电:

Model.scope_with_nulls_first.last

Rails尝试生成无效的SQL时reverse_order.first,将尝试调用,并且与reverse_order并不兼容NULLS LAST

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

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


2

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

@invoice.invoice_lines.order('start_date ASC, end_date ASC NULLS FIRST')


-3

如果想要跨数据库类型的一致结果,似乎必须在Ruby中执行此操作,因为数据库本身会解释NULLS是位于列表的顶部还是末尾。

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

但这不是很有效。

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.