web-dev-qa-db-ja.com

Ruby on Rails-CSVファイルからデータをインポートする

CSVファイルから既存のデータベーステーブルにデータをインポートしたいと思います。 CSVファイルを保存するのではなく、そこからデータを取得して既存のテーブルに配置します。 Ruby 1.9.2とRails 3を使用しています。

これは私のテーブルです:

create_table "mouldings", :force => true do |t|
  t.string   "suppliers_code"
  t.datetime "created_at"
  t.datetime "updated_at"
  t.string   "name"
  t.integer  "supplier_id"
  t.decimal  "length",         :precision => 3, :scale => 2
  t.decimal  "cost",           :precision => 4, :scale => 2
  t.integer  "width"
  t.integer  "depth"
end

これを行うための最良の方法を示すためのコードを教えていただけますか、ありがとう。

191
freshest
require 'csv'    

csv_text = File.read('...')
csv = CSV.parse(csv_text, :headers => true)
csv.each do |row|
  Moulding.create!(row.to_hash)
end
360
yfeldblum

Yfeldblumの答えのよりシンプルなバージョン、それはよりシンプルで、大きなファイルでもうまく機能します:

require 'csv'    

CSV.foreach(filename, :headers => true) do |row|
  Moulding.create!(row.to_hash)
end

With_indifferent_accessやsymbolize_keysは不要で、ファイルを最初に文字列に読み込む必要もありません。

ファイル全体を一度にメモリに保持するのではなく、行ごとに読み取り、行ごとにモールディングを作成します。

187
Tom De Leu

smarter_csv gemは、このユースケースのために特別に作成されました。CSVファイルからデータを読み取り、データベースエントリをすばやく作成します。

  require 'smarter_csv'
  options = {}
  SmarterCSV.process('input_file.csv', options) do |chunk|
    chunk.each do |data_hash|
      Moulding.create!( data_hash )
    end
  end

オプションchunk_sizeを使用して、一度にN個のcsv行を読み取ってから、内側のループでResqueを使用して、すぐに作成するのではなく、新しいレコードを作成するジョブを生成できます。これにより、生成の負荷を分散できます。複数のワーカーへのエントリ。

参照: https://github.com/tilo/smarter_csv

10
Tilo

Upsert を試すことができます:

require 'upsert' # add this to your Gemfile
require 'csv'    

u = Upsert.new Moulding.connection, Moulding.table_name
CSV.foreach(file, headers: true) do |row|
  selector = { name: row['name'] } # this treats "name" as the primary key and prevents the creation of duplicates by name
  setter = row.to_hash
  u.row selector, setter
end

これが必要な場合は、テーブルから自動インクリメントの主キーを取り除き、主キーをnameに設定することも検討してください。あるいは、主キーを形成する属性の組み合わせがある場合は、それをセレクターとして使用します。インデックスは必要ありません、それはただそれをより速くします。

4
Seamus Abshere

これが役立ちます。コード例もあります:

http://csv-mapper.rubyforge.org/

または、同じことを行うためのすくいタスクの場合:

http://erikonrails.snowedin.net/?p=212

4
Kalyan Maddu

データベース関連プロセスをtransactionブロック内にラップすることをお勧めします。コードスニペットブローは、一連の言語を言語モデルにシードする完全なプロセスです。

require 'csv'

namespace :lan do
  desc 'Seed initial languages data with language & code'
  task init_data: :environment do
    puts '>>> Initializing Languages Data Table'
    ActiveRecord::Base.transaction do
      csv_path = File.expand_path('languages.csv', File.dirname(__FILE__))
      csv_str = File.read(csv_path)
      csv = CSV.new(csv_str).to_a
      csv.each do |lan_set|
        lan_code = lan_set[0]
        lan_str = lan_set[1]
        Language.create!(language: lan_str, code: lan_code)
        print '.'
      end
    end
    puts ''
    puts '>>> Languages Database Table Initialization Completed'
  end
end

以下のスニペットはlanguages.csvファイルの一部です。

aa,Afar
ab,Abkhazian
af,Afrikaans
ak,Akan
am,Amharic
ar,Arabic
as,Assamese
ay,Aymara
az,Azerbaijani
ba,Bashkir
...
2
Xinyang Li

私はそれが古い質問であることを知っていますが、それはまだグーグルの最初の10のリンクにあります。

行を1つずつ保存するのはあまり効率的ではありません。ループ内でデータベース呼び出しが発生し、特にデータの膨大な部分を挿入する必要がある場合は、それを避けるほうがよいからです。

バッチ挿入を使用することをお勧めします(非常に高速です)。

INSERT INTO `mouldings` (suppliers_code, name, cost)
VALUES
    ('s1', 'supplier1', 1.111), 
    ('s2', 'supplier2', '2.222')

このようなクエリを手動で作成し、Model.connection.execute(RAW SQL STRING)(推奨されません)を実行するか、gem activerecord-importを使用することができます(2010年8月11日に最初にリリースされました)。この場合、配列rowsにデータを入れてModel.import rowsを呼び出すだけです

詳細についてはgemのドキュメントを参照

0
Yaroslav

より良い方法は、rakeタスクに含めることです。/lib/tasks /内にimport.rakeファイルを作成し、このコードをそのファイルに配置します。

desc "Imports a CSV file into an ActiveRecord table"
task :csv_model_import, [:filename, :model] => [:environment] do |task,args|
  lines = File.new(args[:filename], "r:ISO-8859-1").readlines
  header = lines.shift.strip
  keys = header.split(',')
  lines.each do |line|
    values = line.strip.split(',')
    attributes = Hash[keys.Zip values]
    Module.const_get(args[:model]).create(attributes)
  end
end

その後、端末でこのコマンドを実行しますrake csv_model_import[file.csv,Name_of_the_Model]

0
Ipsagel

このgemを使用します: https://rubygems.org/gems/active_record_importer

class Moulding < ActiveRecord::Base
  acts_as_importable
end

次に、以下を使用できます。

Moulding.import!(file: File.open(PATH_TO_FILE))

ヘッダーがテーブルの列名と一致していることを確認してください

0
Michael Nera