【问题标题】:Rails 3, custom raw SQL insert statementRails 3、自定义原始SQL插入语句
【发布时间】:2012-10-04 23:50:30
【问题描述】:

我有一个评估模型。评价有很多分数。每当创建新的评估时,都会为需要评估的每个用户创建一个分数记录(请参阅下面我正在使用的当前方法。)因此,例如,可能一次创建 40 个分数记录。然后,评估所有者使用用户的分数更新每个分数记录。

我希望使用原始 SQL,因为每个插入都是自己的事务并且速度很慢。

我想使用原始 SQL 将以下内容转换为批量插入语句:

def build_evaluation_score_items
  self.job.active_employees.each do |employee|
    employee_score = self.scores.build
    employee_score.user_id = employee.id
    employee_score.save
  end
end

对如何做到这一点有任何想法吗?我已经尝试改编来自 Chris Heald 的 Coffee Powered 网站的代码示例,但是没有骰子。

感谢任何愿意提供帮助的人!

编辑 1

我没有提到当前方法被包装在一个事务中。

因此,本质上,我试图将其添加到代码块中,以便将所有内容都插入到一个语句中(** 此代码片段来自讨论该主题的 Chris Heald 的 Coffee Powered 网站。我会在那里提出问题,但帖子已超过 3 岁。):

inserts = []
TIMES.times do
  inserts.push "(3.0, '2009-01-23 20:21:13', 2, 1)"
end
sql = "INSERT INTO user_node_scores (`score`, `updated_at`, `node_id`, `user_id`)VALUES #{inserts.join(", ")}"

我很乐意展示我的一些尝试失败的代码...

再次感谢!

好吧,我拼凑了一些类似于上面代码的东西,但我在 ('evaluation_id' 部分周围出现 SQL 语句无效错误。有什么想法吗?

def build_evaluation_score_items
  inserts = []
  self.job.active_employees.each do |employee|
    inserts.push "(#{self.id}, #{employee.id}, #{Time.now}, #{Time.now})"
  end
  sql = "INSERT INTO scores ('evaluation_id', `user_id`, 'created_at', `updated_at`)VALUES #{inserts.join(", ")}"
  ActiveRecord::Base.connection.execute(sql) 
end

你知道上面的 SQL 代码是什么导致了错误吗?

【问题讨论】:

  • created_at 部分对我不起作用,因为它正在创建一个 postgres 无法解析的字符串。 ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: syntax error at or near "07" LINE 4: (1, 1, 100, 2017-02-20 07:00:56 +0000, 2017-02-20 07...

标签: ruby-on-rails ruby-on-rails-3


【解决方案1】:

好吧,经过多次尝试和错误,这是最终答案。很酷的是所有记录都是通过一个语句插入的。当然,会跳过验证(因此,如果您需要在创建时进行模型验证,这将不合适)但在我的情况下,这不是必需的,因为我所做的只是为每个员工的评估设置分数记录。当然,当工作负责人更新员工的评估分数时,验证会按预期工作。

def build_evaluation_score_items
  inserts = []
  time = Time.now.to_s(:db)
  self.job.active_employees.each do |employee|
    inserts.push "(#{self.id}, #{employee.id}, '#{time}')"
  end
  sql = "INSERT INTO scores (evaluation_id, user_id, created_at) VALUES #{inserts.join(", ")}"
  ActiveRecord::Base.connection.execute(sql) 
end

【讨论】:

  • 我使用您的解决方案为我自己的需要组合了一些东西。我正在做一些性能测试,需要用大量随机数据(数百万行)快速(即不使用 ActiveRecord)填充数据库。
  • 这对我不起作用,因为time 的输出错误。 Postgres 抛出这个:ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: syntax error at or near "07" LINE 4: (1, 1, 100, 2017-02-20 07:04:11, 2017-02-20 07:04:11...
【解决方案2】:

我推荐activerecord-import gem,而不是直接构建 SQL(并让自己面临 SQL 注入和其他问题)。它可以发出多行INSERT 命令,以及其他策略。

你可以这样写:

def build_evaluation_score_items
  new_scores = job.active_employees.map do |employee|
    scores.build(:user_id => employee.id)
  end
  Score.import new_scores
end

【讨论】:

    【解决方案3】:

    我认为您正在寻找的是:

    def build_evaluation_score_items
      ActiveRecord::Base.transaction do
        self.job.active_employees.each do |employee|
          employee_score = self.scores.build
          employee_score.user_id = employee.id
          employee_score.save
        end
      end
    end
    

    所有子事务都会自动“推送”到父事务。这将避免大量事务的开销,并且应该会提高性能。

    您可以阅读有关 ActiveRecord 事务的更多信息here

    更新

    对不起,我误会了。为后代保留上述答案。试试这个:

    def build_evaluation_score_items
      raw_sql = "INSERT INTO your_table ('user_id', 'something_else') VALUES "
      insert_values = "('%s', '%s'),"
      self.job.active_employees.each do |employee|
        raw_sql += insert_values % employee.id, "something else"
      end
      ActiveRecord::Base.connection.execute raw_sql
    end
    

    【讨论】:

    • 感谢您的建议。我忽略了在我的帖子中指出我目前将语句包装在代码中的事务中,但这仍然不是最佳的,因为它仍然为每个员工执行插入语句。事务的唯一好处是数据库索引没有更新。
    • 我用一个潜在的解决方案更新了我的答案。如果对您有帮助,请告诉我。
    • 嗨 Chuck - 我同意将第一项保留在那里,这就是我赞成它的原因。交易方法很有意义。也就是说,不幸的是,我收到以下错误:ActiveRecord::Base:Class 的未定义方法“执行”。我会四处寻找合适的执行命令并报告。
    猜你喜欢
    • 2021-11-04
    • 1970-01-01
    • 2011-05-16
    • 2010-11-08
    • 2015-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-14
    相关资源
    最近更新 更多