Rails:最后以空值排序

发布于 2024-11-04 04:09:32 字数 398 浏览 0 评论 0原文

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

我有某些记录,其中值是可选的,因此有些记录具有值,有些记录对该列为空。

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

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

如果我这样做Photo.order('collection_id desc) 然后在 SQLite 上我最后得到空值,但在 PostgreSQL 上我首先得到空值。

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

In my Rails app I've run into an issue a couple times that I'd like to know how other people solve:

I have certain records where a value is optional, so some records have a value and some are null for that column.

If I order by that column on some databases the nulls sort first and on some databases the nulls sort last.

For instance, I have Photos which may or may not belong to a Collection, ie there are some Photos where collection_id=nil and some where collection_id=1 etc.

If I do Photo.order('collection_id desc) then on SQLite I get the nulls last but on PostgreSQL I get the nulls first.

Is there a nice, standard Rails way to handle this and get consistent performance across any database?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

一瞬间的火花 2024-11-11 04:09:33

尽管现在已经是 2017 年了,但对于 NULL 是否应该优先尚未达成共识。如果您没有明确说明,您的结果将根据 DBMS 的不同而有所不同。

该标准没有指定 NULL 与非 NULL 值相比应如何排序,但任何两个 NULL 都被视为同等排序,并且 NULL 应该排序在所有非 NULL 值之上或之下。< /p>

来源,大多数 DBMS 的比较

为了说明这个问题,我编制了一个列表在 Rails 开发中,有几个最常见的情况:

PostgreSQL

NULL 具有最高的值。

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

来源:PostgreSQL 文档

MySQL

NULL s 具有最低值。

执行 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

Even though it's 2017 now, there is still yet to be a consensus on whether NULLs should take precedence. Without you being explicit about it, your results are going to vary depending on the DBMS.

The standard doesn't specify how NULLs should be ordered in comparison with non-NULL values, except that any two NULLs are to be considered equally ordered, and that NULLs should sort either above or below all non-NULL values.

source, comparison of most DBMSs

To illustrate the problem, I compiled a list of a few most popular cases when it comes to Rails development:

PostgreSQL

NULLs have the highest value.

By default, null values sort as if larger than any non-null value.

source: PostgreSQL documentation

MySQL

NULLs have the lowest value.

When doing an ORDER BY, NULL values are presented first if you do ORDER BY ... ASC and last if you do ORDER BY ... DESC.

source: MySQL documentation

SQLite

NULLs have the lowest value.

A row with a NULL value is higher than rows with regular values in ascending order, and it is reversed for descending order.

source

Solution

Unfortunately, Rails itself doesn't provide a solution for it yet.

PostgreSQL specific

For PostgreSQL you could quite intuitively use:

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

MySQL specific

For MySQL, you could put the minus sign upfront, yet this feature seems to be undocumented. Appears to work not only with numerical values, but with dates as well.

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

PostgreSQL and MySQL specific

To cover both of them, this appears to work:

Photo.order('collection_id IS NULL, collection_id DESC') # NULLs come last

Still, this one does not work in SQLite.

Universal solution

To provide cross-support for all DBMSs you'd have to write a query using CASE, already suggested by @PhilIT:

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

which translates to first sorting each of the records first by CASE results (by default ascending order, which means NULL values will be the last ones), second by calculation_id.

夏有森光若流苏 2024-11-11 04:09:33

在column_name前面加上减号并反转顺序方向。它适用于 mysql。 更多详细信息

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

Put minus sign in front of column_name and reverse the order direction. It works on mysql. More details

Product.order('something_date ASC') # NULLS came first
Product.order('-something_date DESC') # NULLS came last
夜血缘 2024-11-11 04:09:33
Photo.order('collection_id DESC NULLS LAST')

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

Photo.order('collection_id DESC NULLS LAST')

I know this is an old one but I just found this snippet and it works for me.

漆黑的白昼 2024-11-11 04:09:33

虽然有点晚了,但有一个通用的 SQL 方法可以做到这一点。像往常一样,CASE 来救援。

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

Bit late to the show but there is a generic SQL way to do it. As usual, CASE to the rescue.

Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
刘备忘录 2024-11-11 04:09:33

Rails 6.1 向 Arel for 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

Rails 6.1 adds nulls_first and nulls_last methods to Arel for PostgreSQL.

Example:

User.order(User.arel_table[:login_count].desc.nulls_last)

Source: https://www.bigbinary.com/blog/rails-6-1-adds-nulls-first-and-nulls-last-to-arel

贵在坚持 2024-11-11 04:09:33

最简单的方法是使用:

.order('name nulls first')

The easiest way is to use:

.order('name nulls first')

戒ㄋ 2024-11-11 04:09:33

为了后代的利益,我想强调与 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 问题中引用了这一点(一个, 两个

  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

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

For posterity's sake, I wanted to highlight an ActiveRecord error relating to NULLS FIRST.

If you try to call:

Model.scope_with_nulls_first.last

Rails will attempt to call reverse_order.first, and reverse_order is not compatible with NULLS LAST, as it tries to generate the invalid SQL:

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

This was referenced a few years ago in some still-open Rails issues (one, two, three). I was able to work around it by doing the following:

  scope :nulls_first, -> { order("table_column IS NOT NULL") }
  scope :meaningfully_ordered, -> { nulls_first.order("table_column ASC") }

It appears that by chaining the two orders together, valid SQL gets generated:

Model Load (12.0ms)  SELECT  "models".* FROM "models"  ORDER BY table_column IS NULL DESC, table_column ASC LIMIT 1

The only downside is that this chaining has to be done for each scope.

小巷里的女流氓 2024-11-11 04:09:33

以下是一些 Rails 6 解决方案。

@Adam Sibik 的答案很好地总结了各种数据库系统之间的差异。

但不幸的是,一些提出的解决方案,包括“通用解决方案”和“PostgreSQL 和 MySQL 特定的”,将不再适用于 Rails 6 (ActiveRecord 6),因为这是因为 order() 规范发生变化,不接受某些原始 SQL(我确认“PostgreSQL 特定”解决方案在 Rails 6.1.4 中仍然有效)。有关此更改的背景,请参阅例如
"Rails 6.1 中 SQL 注入的更新”贾斯汀。

为了避免这个问题,您可以使用 来封装 SQL 语句Arel.sql 如下,其中 NULL 位于最后,假设您 100% 确定您给出的 SQL 语句是安全的。

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

仅供参考,如果您想按布尔列(例如 is_ok)按 [TRUE、FALSE、NULL] 的顺序排序,而不管数据库系统如何,则以下任一方法都可以:

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'))

(注意,SQLite 没有 Boolean 类型,因此前者可能更安全,尽管这并不重要,因为 Rails 应该保证该值为 0 或 1(或 NULL)。)

Here are some Rails 6 solutions.

The answer by @Adam Sibik is a great summary about the difference between various database systems.

Unfortunately, though, some of the presented solutions, including "Universal solution" and "PostgreSQL and MySQL specific", would not work any more with Rails 6 (ActiveRecord 6) as a result of its changed specification of order() not accepting some raw SQLs (I confirm the "PostgreSQL specific" solution still works as of Rails 6.1.4). For the background of this change, see, for example,
"Updates for SQL Injection in Rails 6.1" by Justin.

To circumvent the problem, you can wrap around the SQL statements with Arel.sql as follows, where NULLs come last, providing you are 100% sure the SQL statements you give are safe.

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

Just for reference, if you want to sort by a Boolean column (is_ok, as an example) in the order of [TRUE, FALSE, NULL] regardless of the database systems, either of these should work:

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 does not have the Boolean type and so the former may be safer arguably, though it should not matter because Rails should guarantee the value is either 0 or 1 (or NULL).)

删除→记忆 2024-11-11 04:09:33

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

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

In my case I needed sort lines by start and end date by ASC, but in few cases end_date was null and that lines should be in above, I used

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

望喜 2024-11-11 04:09:33

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

@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

Adding arrays together will preserve order:

@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

淡忘如思 2024-11-11 04:09:33

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

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

但这效率不高。

It seems like you'd have to do it in Ruby if you want consistent results across database types, as the database itself interprets whether or not the NULLS go at the front or end of the list.

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

But that is not very efficient.

感性不性感 2024-11-11 04:09:32

我不是 SQL 专家,但为什么不先按是否为 null 进行排序,然后再按您想要的方式对其进行排序。

Photo.order('collection_id IS NULL, collection_id DESC')     # NULLs last
Photo.order('collection_id IS NOT NULL, collection_id DESC') # NULLs first

如果您只使用 PostgreSQL,您也可以这样做

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

如果您想要通用的东西(比如您在多个数据库中使用相同的查询,您可以使用 (由@philT提供

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

I'm no expert at SQL, but why not just sort by if something is null first then sort by how you wanted to sort it.

Photo.order('collection_id IS NULL, collection_id DESC')     # NULLs last
Photo.order('collection_id IS NOT NULL, collection_id DESC') # NULLs first

If you are only using PostgreSQL, you can also do this

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

If you want something universal (like you're using the same query across several databases, you can use (courtesy of @philT)

Photo.order('CASE WHEN collection_id IS NULL THEN 1 ELSE 0 END, collection_id')
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文