在我的Rails应用程序中,我遇到过几次问题,我想知道其他人如何解决:
我有某些记录,其中值是可选的,因此某些记录具有值,而某些记录的值为null。
如果我在某些数据库上按该列排序,则首先对空值进行排序,在某些数据库上对空值进行排序。
例如,我有可能属于或可能不属于集合的照片,即有些照片中collection_id=nil
和某些collection_id=1
等等。
如果我执行Photo.order('collection_id desc)
那么在SQLite上我得到空值,但在PostgreSQL上我首先获得空值。
是否有一种很好的标准Rails方法来处理这个并在任何数据库中获得一致的性能?
答案 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值之上或之下排序
为了说明这个问题,我在Rails开发中编制了一些最常见的案例列表:
NULL
具有最高价值。
默认情况下,null值的排序方式大于任何非空值。
NULL
的值最低。
在执行ORDER BY时,如果您执行ORDER BY ... ASC,则首先显示NULL值,如果执行ORDER BY ... DESC,则显示最后一个。
NULL
的值最低。
具有NULL值的行高于具有常规值的行按升序排列,并且按降序顺序颠倒。
不幸的是,Rails本身并没有为它提供解决方案。
对于PostgreSQL,您可以非常直观地使用:
Photo.order('collection_id DESC NULLS LAST') # NULLs come last
对于MySQL,您可以预先设置减号,但此功能似乎没有记录。似乎不仅可以使用数值,还可以使用日期。
Photo.order('-collection_id DESC') # NULLs come last
要覆盖它们,这似乎有效:
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_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问题(one,two,three)中引用了这一点。我能够通过以下方式解决这个问题:
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
答案 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}
但这不是很有效。