【问题标题】:Rails raw SQL exampleRails 原始 SQL 示例
【发布时间】:2013-01-27 06:45:18
【问题描述】:

如何将此代码转换为原始 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)

【问题讨论】:

  • 为什么你认为原始 SQL 会更快?你怎么知道是 SQL 问题?
  • 没有看到查询计划,我猜你遇到的问题是按 created_at 排序的。您可能正在对整个表进行 seq 扫描(哦,还引入了项目表)。在一个大表和动力不足的数据库上使用一次控制器方法执行其中两个(heroku 数据库进行了通用调整,并且相对于您花费的 $$ 而言动力相对不足),您可能会遇到超时。如果您没有在这些表中进行大量插入操作,则可以进行排序索引:devcenter.heroku.com/articles/postgresql-indexes#sorted-indexes
  • 好吧,手动剪切 sql 总是会更快(如果您知道自己在做什么)。 Rails 生成了一些非常讨厌的 sql。它运作良好,但要通用,它必须...通用。也就是说,Jim 可能是对的……创建索引会更好。尝试在 pgadmin 中运行您的查询(针对您的数据库)

标签: sql ruby-on-rails


【解决方案1】:

你可以这样做:

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

records_array 将是您的 sql 查询的结果,您可以在一个数组中进行迭代。

【讨论】:

  • 旁注:records_array 将针对不同的数据库适配器具有不同的类型。如果您使用 PG,它将是 PG::Result 的实例,而不是 Array
  • 然后您需要在此 PG::Result 对象上调用 values 以获取结果数组
  • @baash05 “赞成通过类访问它”是什么意思。 - 这讨论了如何在没有模型的情况下访问表格
  • 其实OP中没有提到不使用模型。只是如何执行原始sql。 PaymentDetail.execute(sql) 有效。
  • 我更喜欢ActiveRecord::Base.connection.exec_query,因为它返回一个ActiveRecords::Result 对象,该对象具有方便的方法,如.columns.rows 来访问标题和值。来自.execute 的哈希数组处理起来很麻烦,并且当我使用 SUM GROUP BY 子句运行时给了我多余的结果。
【解决方案2】:

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

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 - Findinig by SQL
  2. Ruby on Rails - Active Record Result

【讨论】:

  • #find_by_sql 正是我想要的,非常感谢。
  • #find_by_sql 是吗!!
【解决方案3】:

您可以使用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)

【讨论】:

  • 嗨.. 有没有办法将参数传递给这个查询?
  • @AkshayGoyal 您可以在块内使用普通的 ruby​​ 字符串插值。 SELECT * FROM users WHERE users.id = #{ user.id }
  • 虽然您可以这样做,但可能会使您面临 SQL 注入的可能性
【解决方案4】:

您可以执行直接 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

编辑:正如 Huy 所说,一个简单的方法是ActiveRecord::Base.connection.execute("...")。另一种方式是ActiveRecord::Base.connection.exec_query('...').rows。您可以使用本机准备好的语句,例如如果使用 postgres,prepared 语句可以使用 raw_connection、prepare 和 exec_prepared 完成,如https://stackoverflow.com/a/13806512/178651中所述

您还可以将原始 SQL 片段放入 ActiveRecord 关系查询中:http://guides.rubyonrails.org/active_record_querying.html 在关联、范围等方面。您可能可以使用 ActiveRecord 关系查询构造相同的 SQL,并且可以使用 ARel 做一些很酷的事情,正如 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 表,然后为该视图创建一个模型(它将是只读的)。一些 DB 支持物化视图,这可能是提高性能的好主意。

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

【讨论】:

  • 既然提问者是按日期排序的,我认为分页不会有帮助吗?我对 SQL 不是最深入的,但我的理解是原始帖子中的查询需要获取整个表才能对其进行排序 - 只有这样它才能限制结果。我怀疑大部分查询计划都在 order 子句中。
  • @JimWrubel 澄清了关于分页的段落——措辞有点不妥。
【解决方案5】:

我想使用ActiveRecord 类的exec_query,因为它返回查询转换为对象的映射,因此当主题是原始 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"]

【讨论】:

    【解决方案6】:

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

    my_instances = MyModel.where.not(attribute_a: nil) \
      .where('crc32(attribute_b) = ?', slot) \
      .select(:id)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-28
      • 1970-01-01
      • 2011-12-11
      • 2016-03-27
      • 2013-06-11
      相关资源
      最近更新 更多