0

以下は、インターネットから約9000行のtxtファイルをダウンロードしてデータベースに入力するコードです。多くのことを試しましたが、7分以上かかります。win 7 64 ビットと 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 行あります。これらすべてを組み合わせて挿入し、プロシージャを 1 回だけ呼び出すにはどうすればよいでしょうか。また、mysqlはルーチン内でそのようなことを行うために配列と反復をサポートしていますか? 提案をお願いします。ありがとうございます。

編集

テーブルが既に存在するかどうかに応じて、テーブルに挿入する必要があります。また、テーブルに挿入する前に条件付き比較を使用する必要があります。これらの SQL ステートメントを書くことは絶対にできないので、SQL ストアド プロシージャを作成しました。これで @the_data というリストができました。それをプロシージャに渡し、MySQL 側ですべてを反復するにはどうすればよいでしょうか。何か案は ?

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

これにより 100 回の挿入が行われますが、そのうちの 35 回は冗長であるため、挿入を行う前にテーブルで既存のエントリを検索する必要があります。

何か案は ?ありがとう

4

2 に答える 2

5

コメントから、DBクエリの実行にすべての時間を費やしているようです。最近のRubyプロジェクトでは、CSVファイルからデータベースにデータをインポートするスローコードも最適化する必要がありました。INSERTCSVファイルの各行に1つのクエリを使用するのではなく、単一の一括クエリを使用してすべてのデータをインポートすることで、パフォーマンスが約500倍向上しました。すべてのデータを配列に蓄積し、文字列補間とを使用して単一のSQLクエリを作成しArray#joinました。

あなたのコメントから、バルクの動的SQLを構築して実行する方法がわからないようですINSERT。まず、既知の順序で挿入されるフィールドを使用して、ネストされた配列でデータを取得します。例として、次のようなデータがあると想像してください。

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

于 2012-07-01T13:38:58.980 に答える
1

データを csv としてエクスポートし、'load data infile... replace' でロードしてみてください。一括挿入クエリを構築しようとするよりもクリーン/簡単に思えます。

于 2012-07-02T00:41:57.037 に答える