Rails:以空排序



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

我有一些记录,其中值是可选的,所以有些记录有值,有些记录为空。

如果我按列排序,在某些数据库中空值优先排序,在某些数据库中空值最后排序。

例如,我的照片可能属于也可能不属于一个集合,即有些照片属于collection_id=nil,有些照片属于collection_id=1等。

如果我做Photo.order('collection_id desc)然后在SQLite上我得到空最后,但在PostgreSQL上我得到空第一。

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

我不是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的值最高。

默认情况下,空值排序好像比任何非空值大。

来源:PostgreSQL文档

MySQL

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特定

对于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_firstnulls_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_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问题(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}

相关内容

  • 没有找到相关文章

最新更新