如何将数组作为绑定变量传递给Rails/ActiveRecord原始SQL查询



我需要将一个id数组传递到我的原始sql查询中,如下所示:

select offers.* from offers where id in (1,2,3,4,5)

真正的查询包含许多联接和聚合函数,不能使用Arel表达式或像Offer.where(id: [...])这样的ActiveRecord模型方法编写。我正在寻找如何在原始查询中使用绑定变量。

我不想在字符串中插入id,而是想使用这样的绑定变量(伪代码):

ActiveRecord::Base.connection.select_all("select offers.* from offers where id in (:ids)", {ids: [1,2,3,4,5]})

然而,我找不到任何解决方案来执行此操作。从这张票上,我得到了一个关于ActiveRecord代码中相关测试用例的评论,下面是一个例子:

sub   = Arel::Nodes::BindParam.new
binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)]
sql   = "select * from topics where id = #{sub.to_sql}"
@connection.exec_query(sql, "SQL", binds)

我尝试过这种方法,但它根本不起作用,我的"?"并没有被实际值所取代。

我使用的是Rails 5.1.6和MariaDB数据库。

纯使用arel可以以简单得多的方式完成此操作。(它还使代码比SQL字符串更易于维护)

offers = Arel::Table.new('offers') 
ids = [1,2,3,4,5]
query = offers.project(Arel.star).where(offers[:id].in(ids))
ActiveRecord::Base.connection.exec_query(query.to_sql)

这将导致以下SQL

SELECT 
[offers].*
FROM 
[offers]
WHERE 
[offers].[id] IN (1,2,3,4,5)

执行时,您将收到一个ActiveRecord::Result对象,该对象通常最容易通过调用to_hash来处理,生成的每一行都将变成{column_name => value}Hash

但是,如果您使用的是rails,并且Offer是一个真正的型号,那么:

Offer.where(id: ids)

将导致相同的查询,并将返回Offer对象的ActiveRecord::Relation集合,这通常更可取。

更新

似乎需要在mysql2中启用prepared_statements(mariadb)才能使用绑定参数,可以这样做:

default: &default
adapter: mysql2
encoding: utf8
prepared_statements: true  # <- here we go!

请注意以下代码:https://github.com/rails/rails/blob/5-1-stable/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L115

https://github.com/rails/rails/blob/5-1-stable/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb#L40

https://github.com/rails/rails/blob/5-1-stable/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L630

https://github.com/rails/rails/blob/5-1-stable/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb#L30

正如您在上一个代码中看到的,如果prepared_statements关闭(这似乎是mysql2适配器的默认设置),exec_query将忽略bind_params

最新更新