Rails原始SQL示例


239

如何将此代码转换为原始sql并在rails中使用?因为当我在heroku中部署此代码时,有一个请求超时错误。我认为如果使用原始sql会更快。

@payments = PaymentDetail.joins(:project).order('payment_details.created_at desc')
@payment_errors = PaymentError.joins(:project).order('payment_errors.created_at desc')

@all_payments = (@payments + @payment_errors)

3
您为什么认为原始SQL会更快?您怎么知道它是SQL问题?
John Naegle

在没有看到查询计划的情况下,我想您所遇到的问题是由created_at订购的。您可能正在对整个表进行seq扫描(哦,还引入了项目表)。在大表上一次执行控制器方法中的两个,在功能不足的数据库上进行操作(heroku数据库已进行一般性调整,对于花费的$$而言功能相对不足),您很可能会超时。如果您没有在这些表中进行大量插入操作,则可以进行排序索引:devcenter.heroku.com/articles/postgresql-indexes#sorted-indexes
Jim Wrubel

1
好吧,手动剪切sql总是更快(如果您知道自己在做什么)。Rails制作了一些非常讨厌的sql。它运作良好,但要具有通用性,就必须具有通用性。也就是说,Jim可能是正确的。创建索引会更好。尝试在pgadmin中运行查询(针对您的数据库)
baash05

Answers:


427

你可以这样做:

sql = "Select * from ... your sql query here"
records_array = ActiveRecord::Base.connection.execute(sql)

records_array 然后将是您可以循环访问的数组中sql查询的结果。


52
旁注:records_array对于不同的数据库适配器,将具有不同的类型。如果您使用的是PG,它将是的实例PG::Result,而不是Array
tybro0103

58
然后您需要调用valuesPG::Result对象以获取结果数组
jobwat 2014年

3
@ baash05“支持通过类访问它”是什么意思。-谈论如何在没有模型的情况下访问表格
Yo Ludke 2014年

1
实际上,没有提到在OP中不使用模型。只是如何执行原始sql。PaymentDetail.execute(sql)有效。
baash05 2014年

20
ActiveRecord::Base.connection.exec_query更喜欢,因为它返回的ActiveRecords::Result对象具有方便的方法,例如.columns.rows访问标头和值。.execute当我使用SUM GROUP BY子句运行时,散列的数组可能很难处理,并且给了我多余的结果。
Francio Rodrigues

181

我知道这很旧了...但是今天我遇到了同样的问题,找到了解决方案:

Model.find_by_sql

如果要实例化结果:

Client.find_by_sql("
  SELECT * FROM clients
  INNER JOIN orders ON clients.id = orders.client_id
  ORDER BY clients.created_at desc
")
# => [<Client id: 1, first_name: "Lucas" >, <Client id: 2, first_name: "Jan">...]

Model.connection.select_all('sql').to_hash

如果只想散列值:

Client.connection.select_all("SELECT first_name, created_at FROM clients
   WHERE id = '1'").to_hash
# => [
  {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"},
  {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"}
]

结果对象:

select_all返回一个result对象。您可以用它来做魔术。

result = Post.connection.select_all('SELECT id, title, body FROM posts')
# Get the column names of the result:
result.columns
# => ["id", "title", "body"]

# Get the record values of the result:
result.rows
# => [[1, "title_1", "body_1"],
      [2, "title_2", "body_2"],
      ...
     ]

# Get an array of hashes representing the result (column => value):
result.to_hash
# => [{"id" => 1, "title" => "title_1", "body" => "body_1"},
      {"id" => 2, "title" => "title_2", "body" => "body_2"},
      ...
     ]

# ActiveRecord::Result also includes Enumerable.
result.each do |row|
  puts row['title'] + " " + row['body']
end

资料来源:

  1. ActiveRecord-SQL的Findinig
  2. Ruby on Rails-活动记录结果

9
#find_by_sql正是我想要的,非常感谢。
Shelvacu '16

#find_by_sql是!!
史蒂文·L。

28

您可以使用来执行原始查询ActiveRecord。我建议使用SQL块

query = <<-SQL 
  SELECT * 
  FROM payment_details
  INNER JOIN projects 
          ON projects.id = payment_details.project_id
  ORDER BY payment_details.created_at DESC
SQL

result = ActiveRecord::Base.connection.execute(query)

嗨..有什么办法可以将参数传递给该查询吗?
Akshay Goyal,

@AkshayGoyal,您可以在块内使用常规的红宝石字符串插值。SELECT * FROM users WHERE users.id = #{ user.id }
内森·贝克

2
虽然可以做到,但可能会使您暴露出SQL注入的可能性
Deepak Mahakale,

26

您可以直接使用SQL对两个表进行单个查询。我将提供一个经过清理的查询示例,以希望避免人们直接将变量直接放入字符串本身(SQL注入危险),即使该示例未指定需要它:

@results = []
ActiveRecord::Base.connection.select_all(
  ActiveRecord::Base.send(:sanitize_sql_array, 
   ["... your SQL query goes here and ?, ?, ? are replaced...;", a, b, c])
).each do |record|
  # instead of an array of hashes, you could put in a custom object with attributes
  @results << {col_a_name: record["col_a_name"], col_b_name: record["col_b_name"], ...}
end

编辑:正如休伊所说,一种简单的方法是ActiveRecord::Base.connection.execute("...")。另一种方法是ActiveRecord::Base.connection.exec_query('...').rows。而且,您可以使用本机准备的语句,例如,如果使用postgres,则可以按照https://stackoverflow.com/a/13806512/178651中的描述使用raw_connection,prepare和exec_prepared完成预处理的语句。

您还可以将原始SQL片段放入ActiveRecord关系查询中:http : //guides.rubyonrails.org/active_record_querying.html 以及关联关系,范围等。正如Ernie在http://erniemiller.org/2010/03/28/advanced-activerecord-3-queries-with-arel/中提到的那样。而且,当然还有其他ORM,gem等。

如果要经常使用它,并且添加索引不会引起其他性能/资源问题,请考虑在数据库中为payment_details.created_at和payment_errors.created_at添加一个索引。

如果很多记录而不是所有记录都需要一次显示,请考虑使用分页:

如果需要分页,可以考虑在数据库中首先创建一个名为payment_records的视图,该视图将payment_details和payment_errors表结合在一起,然后为该视图提供一个模型(只读)。一些数据库支持实例化视图,这可能是提高性能的一个好主意。

还请考虑Rails服务器和DB服务器上的硬件或VM规范,配置,磁盘空间,网络速度/延迟/等,邻近性等。如果没有,请考虑将DB与Rails应用程序放在不同的服务器/ VM上,等等。 。


由于请求者按日期排序,所以我认为分页无济于事?我对SQL的了解不是最深,但是我的理解是,原始帖子中的查询需要获取整个表才能对其进行排序-只有这样才能限制结果。我怀疑大部分查询计划都在order子句中。
Jim Wrubel 2013年

@JimWrubel澄清了有关分页的段落-措辞有点不合时宜。
加里·韦弗

2

我想工作,exec_query对中ActiveRecord类,因为它返回的查询转变为对象的映射,所以它会非常实用,富有成效的迭代与对象时,主题是原始的SQL。

例:

values = ActiveRecord::Base.connection.exec_query("select * from clients")
p values

并返回以下完整查询:

[{"id": 1, "name": "user 1"}, {"id": 2, "name": "user 2"}, {"id": 3, "name": "user 3"}]

仅获取值列表

p values.rows

[[1, "user 1"], [2, "user 2"], [3, "user 3"]]

仅获取字段列

p values.columns

["id", "name"]

0

您还可以将原始SQL与ActiveRecord条件混合使用,例如,如果要在条件中调用函数:

my_instances = MyModel.where.not(attribute_a: nil) \
  .where('crc32(attribute_b) = ?', slot) \
  .select(:id)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.