我在这里总结了解决方案,并添加了最近(9.4+)PostgreSQL 特定的解决方案。以下是基于 Rails 6.1 和 PostgreSQL 12 的。虽然我提到了早期版本的 Rails 和 PostgreSQL 的解决方案,但我还没有用早期版本实际测试过它们。
作为参考,这个问题“ORDER BY the IN value list”给出了使用数据库进行排序/排序的各种方式。
在这里,我假设模型保证具有 ID 数组指定的所有记录ids
。否则,可能会引发类似的异常ActiveRecord::RecordNotFound
(或可能不会,取决于方式)。
什么不起作用
Person.where(id: ids)
返回的 Relation 的顺序可以是任意的,也可以是主 ID 的数值顺序;无论如何,它通常不同意 的ids
。
获取数组的简单解决方案
(仅限 Rails 5+(?))
Person.find ids
它按给定的顺序返回一个 Ruby 数组 Person 模型ids
。
缺点是您无法使用 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)
它按给定的顺序返回一个 Ruby 数组 Person 模型ids
。
获取 Relation 的 DB 级解决方案
以下所有返回Person::ActiveRecord_Relation
,您可以根据需要对其应用更多过滤器。
在以下解决方案中,所有记录都被保留,包括那些 ID 未包含在给定数组中的记录ids
。您可以随时通过添加将它们过滤掉where(id: ids)
(这种灵活性是一种美感ActiveRecord_Relation
)。
对于任何数据库
基于user3033467 的回答,但已更新为可与 Rails 6 一起使用(order()
出于安全考虑,它已禁用某些功能;请参阅Justin 的“ Rails 6.1 中的 SQL 注入更新”作为背景)。
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的回答(我没有测试过)。
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 的回答,但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")
获取较低级别的对象
以下是获取较低级别对象的解决方案。在绝大多数情况下,上述解决方案必须优于这些解决方案,但为了完整起见,我将其放在这里(并在我找到更好的解决方案之前记录下来)......</p>
在下面的解决方案中,不匹配 ID 的记录将ids
被过滤掉,这与上一节中描述的解决方案不同(可以选择保留所有记录)。
获取 ActiveRecord::Result
这是ActiveRecord::Result
PostgreSQL 9.4+ 的解决方案。
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
这是PG::Result
PostgreSQL 9.4+ 的解决方案。与上面非常相似,但替换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)