我在这里总结了解决方案,并添加了最近(9.4+)PostgreSQL 特定的解决方案。以下是基于 Rails 6.1 和 PostgreSQL 12 的。虽然我提到了针对早期版本的 Rails 和 PostgreSQL 的解决方案,但我并没有实际测试过早期版本。
作为参考,question "ORDER BY the IN value list" 给出了使用数据库进行排序/排序的各种方法。
在这里,我假设模型保证具有 ID 数组 ids 指定的所有记录。否则,可能会引发像 ActiveRecord::RecordNotFound 这样的异常(也可能不会,取决于方式)。
什么不起作用
Person.where(id: ids)
返回的 Relation 的顺序可以是任意的,也可以是主 ID 的数值顺序;无论哪种情况,它通常与ids的不一致。
获取数组的简单解决方案
(仅限 Rails 5+(?))
Person.find ids
它按照给定的ids 的顺序返回一个 Ruby 人物模型数组。
缺点是您无法使用 SQL 进一步修改结果。
在 Rails 3 中,显然是以下方式,尽管在其他版本的 Rails 中这可能不起作用(当然在 Rails 6 中不起作用)。
Person.find_all_by_id ids
获取数组的纯 Ruby 解决方案
两种方式。无论 Rails 版本如何(我认为),两者都可以工作。
Person.where(id: ids).sort_by{|i| ids.index(i.id)}
Person.where(id: ids).index_by(&:id).values_at(*ids)
它按照给定的ids 的顺序返回一个 Ruby 的 Person 模型数组。
获取关系的数据库级解决方案
以下所有返回Person::ActiveRecord_Relation,如果您愿意,可以对其应用更多过滤器。
在以下解决方案中,所有记录都被保留,包括那些 ID 未包含在给定数组 ids 中的记录。您可以随时通过添加where(id: ids) 将它们过滤掉(这种灵活性是ActiveRecord_Relation 的优点)。
适用于任何数据库
基于 user3033467's answer,但已更新为可与 Rails 6 一起使用(出于安全考虑,Rails 6 已禁用 order() 的某些功能;有关背景信息,请参阅 Justin 的“Updates for SQL Injection in Rails 6.1”)。
order_query = <<-SQL
CASE musics.id
#{ids.map.with_index { |id, index| "WHEN #{id} THEN #{index}" } .join(' ')}
ELSE #{ids.length}
END
SQL
Person.order(Arel.sql(order_query))
对于 MySQL 特定
来自Koen's answer(我没有测试过)。
Person.order(Person.send(:sanitize_sql_array, ['FIELD(id, ?)', ids])).find(ids)
针对 PostgreSQL 特定
PostgreSQL 9.4+
join_sql = "INNER JOIN unnest('{#{ids.join(',')}}'::int[]) WITH ORDINALITY t(id, ord) USING (id)"
Person.joins(join_sql).order("t.ord")
PostgreSQL 8.2+
基于Jerph's answer,但LEFT JOIN替换为INNER JOIN:
val_ids = ids.map.with_index.map{|id, i| "(#{id}, #{i})"}.join(", ")
Person.joins("INNER JOIN (VALUES #{val_ids}) AS persons_id_order(id, ordering) ON persons.id = persons_id_order.id")
.order("persons_id_order.ordering")
获取低级对象
以下是获取较低级别对象的解决方案。
在绝大多数情况下,上述解决方案必须优于这些解决方案,但为了完整起见我放在这里(并在我找到更好的解决方案之前记录)......
在下面的解决方案中,与ids中的ID不匹配的记录将被过滤掉,这与上一节中描述的解决方案不同(可以选择保留所有记录)。
获取 ActiveRecord::Result
这是使用 PostgreSQL 9.4+ 获取 ActiveRecord::Result 的解决方案。
ActiveRecord::Result 类似于哈希数组。
str_sql = "select persons.* from persons INNER JOIN unnest('{#{ids.join(',')}}'::int[]) WITH ORDINALITY t(id, ord) USING (id) ORDER BY t.ord;"
Person.connection.select_all(str_sql)
Person.connection.exec_query 返回相同(别名?)。
获取 PG::Result
这是使用 PostgreSQL 9.4+ 获取 PG::Result 的解决方案。与上面非常相似,但将exec_query 替换为execute(第一行与上面的解决方案相同):
str_sql = "select persons.* from persons INNER JOIN unnest('{#{ids.join(',')}}'::int[]) WITH ORDINALITY t(id, ord) USING (id) ORDER BY t.ord;"
Person.connection.execute(str_sql)