解析ruby中的大文本文件需要花费大量时间?

时间:2012-07-01 11:59:01

标签: ruby

下面是从互联网上下载大约9000行的txt文件并填充数据库的代码,我已经尝试了很多但是需要花费大量时间超过7分钟。我使用win 7 64 bit和ruby 1.9.3。有没有办法更快地完成它?

require 'open-uri'
require 'dbi'
dbh = DBI.connect("DBI:Mysql:mfmodel:localhost","root","")
#file = open('http://www.amfiindia.com/spages/NAV0.txt')
file = File.open('test.txt','r')
lines = file.lines
2.times { lines.next }
curSubType = ''
curType = ''
curCompName = ''
lines.each do |line|
    line.strip!
if line[-1] == ')'
    curType,curSubType = line.split('(')
    curSubType.chop!
elsif line[-4..-1] == 'Fund'
    curCompName = line.split(" Mutual Fund")[0] 
elsif line == ''
    next
else
    sCode,isin_div,isin_re,sName,nav,rePrice,salePrice,date = line.split(';')
    sCode = Integer(sCode)
    sth = dbh.prepare "call mfmodel.populate(?,?,?,?,?,?,?)"
    sth.execute curCompName,curSubType,curType,sCode,isin_div,isin_re,sName
end
end
dbh.do "commit"
dbh.disconnect
file.close


106799;-;-;HDFC ARBITRAGE FUND RETAIL PLAN DIVIDEND OPTION;10.352;10.3;10.352;29-Jun-2012

这是要插入表中的数据格式。现在有8000条这样的行,如何通过组合所有这些来执行插入并仅调用一次该过程。另外,mysql是否支持数组和迭代在例程中做这样的事情。请提出你的建议。谢谢。

修改

我必须根据它们是否已经存在而对表进行插入,我还需要在插入表之前使用条件比较。我绝对不能为这些编写SQL语句,所以我编写了SQL存储过程。现在我有一个@the_data列表,如何将其传递给过程,然后在MySQL端迭代它。有什么想法吗?

insert into mfmodel.company_masters (company_name) values
#{@the_data.map {|str| "('#{str[0]}')"}.join(',')}

这会产生100次插入,但其中35次是多余的,所以我需要在插入之前在表中搜索现有的条目。

任何想法?的感谢

2 个答案:

答案 0 :(得分:5)

从您的评论中,您似乎花费了所有时间来执行数据库查询。在最近的Ruby项目中,我还必须优化一些慢速代码,这些代码将CSV文件中的数据导入数据库。通过使用单个批量INSERT查询导入所有数据,而不是对CSV文件的每一行进行1次查询,我的性能提升了500倍。我在数组中累积了所有数据,然后使用字符串插值和Array#join构建了一个SQL查询。

从您的评论中,您可能不知道如何为批量INSERT构建和执行动态SQL。首先在嵌套数组中获取数据,并以已知顺序插入字段。举个例子,假设我们有这样的数据:

some_data = [['106799', 'HDFC FUND'], ['112933', 'SOME OTHER FUND']]

您似乎正在使用Rails和MySQL,因此动态SQL必须使用MySQL语法。要构建和执行INSERT,您可以执行以下操作:

ActiveRecord::Base.connection.execute(<<SQL)
  INSERT INTO some_table (a_column, another_column)
  VALUES #{some_data.map { |num,str| "(#{num},'#{str}')" }.join(',')};
SQL

您说您需要将数据插入2个不同的表中。那不是问题;只是在不同的数组中累积每个表的数据,并执行2个动态查询,可能在事务内部。 2个查询将比9000快得多。

同样,你在评论中说过你可能需要更新一些记录而不是插入。我在上面提到的“CSV导入”案例中也是这种情况。解决方案只是稍微复杂一点:

# sometimes code speaks more eloquently than prose
require 'set'
already_imported = Set.new
MyModel.select("unique_column_which_also_appears_in_imported_files").each do |x|
  already_imported << x.unique_column_which_also_appears_in_imported_files
end

to_insert,to_update = [],[]
imported_data.each do |row|
  # for the following line, don't let different data types 
  #   (like String vs. Numeric) get ya
  # if you need to convert the imported data to match correctly against what's
  #   already in the DB, do it!
  if already_imported.include? row[index_of_unique_column]
    to_update << row
  else
    to_insert << row
  end
end

然后,您必须为所涉及的每个表构建动态INSERT 动态UPDATE。如果您需要,可以使用Google UPDATE语法,并使用您喜欢的所有字符串处理功能!

回到上面的示例代码,请注意数字和字符串字段之间的区别。如果字符串可能包含单引号,则必须确保所有单引号都被转义。当您尝试执行此操作时,String#gsub的行为可能会让您感到惊讶:它为\'赋予了特殊含义。到目前为止我发现的逃避单引号的最佳方法是:string.gsub("'") { "\\'" }。也许其他海报知道更好的方式。

如果要插入日期,请​​确保将它们转换为MySQL的日期语法。

是的,我知道“自己动手”的SQL清理是非常不确定的。上述方法甚至可能存在安全漏洞;如果是这样,我希望我的消息灵通的同行会让我直截了当。但性能提升太大了,不容忽视。同样,如果可以使用带有占位符的准备查询来完成,并且您知道如何做,请发布!

查看代码,看起来您正在使用存储过程(mfmodel.populate)插入数据。即使您确实想要使用存储过程,为什么循环中有dbh.prepare?您应该能够将该行移到lines.each之外。

答案 1 :(得分:1)

您可能希望尝试将数据导出为csv并使用'load data infile ... replace'加载它。它似乎比尝试构造批量插入查询更简洁/更容易。