关于Ruby on Rails:Ruby on Rails – 从CSV文件导入数据

Ruby on Rails - Import Data from a CSV file

我想将CSV文件中的数据导入现有数据库表。 我不想保存CSV文件,只需从中获取数据并将其放入现有表中。 我正在使用Ruby 1.9.2和Rails 3。

这是我的表:

1
2
3
4
5
6
7
8
9
10
11
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

你能给我一些代码来告诉我最好的方法吗,谢谢。


1
2
3
4
5
6
7
require 'csv'    

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


更简单的yfeldblum的答案,更简单,也适用于大文件:

1
2
3
4
5
require 'csv'    

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

不需要with_indifferent_access或symbolize_keys,也不需要先将文件读入字符串。

它不会立即将整个文件保存在内存中,而是逐行读取并在每行创建一个Molding。


smarter_csv gem专为此用例创建:从CSV文件读取数据并快速创建数据库条目。

1
2
3
4
5
6
7
  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-rows,然后在内部循环中使用Resque生成将创建新记录的作业,而不是立即创建它们 - 这样您就可以分散负载为多个工人生成条目。

也可以看看:
https://github.com/tilo/smarter_csv


您可以尝试Upsert

1
2
3
4
5
6
7
8
9
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。或者,如果存在形成主键的某些属性组合,请将其用作选择器。不需要索引,只会让它更快。


这可以帮助。它也有代码示例:

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

或者执行相同的rake任务:

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


最好将数据库相关进程包装在transaction块中。代码片段是将一组语言播种到语言模型的完整过程,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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文件的一部分,

1
2
3
4
5
6
7
8
9
10
11
aa,Afar
ab,Abkhazian
af,Afrikaans
ak,Akan
am,Amharic
ar,Arabic
as,Assamese
ay,Aymara
az,Azerbaijani
ba,Bashkir
...


我知道这是一个古老的问题,但它仍然在谷歌的前10个链接。

逐行保存行并不是非常有效,因为它会导致循环中的数据库调用,您最好避免这种情况,特别是当您需要插入大量数据时。

使用批量插入更好(并且速度更快)。

1
2
3
4
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文档


更好的方法是将其包含在rake任务中。在/ lib / tasks /中创建import.rake文件,并将此代码放到该文件中。

1
2
3
4
5
6
7
8
9
10
11
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]中运行此命令


使用这个宝石:
https://rubygems.org/gems/active_record_importer

1
2
3
class Moulding < ActiveRecord::Base
  acts_as_importable
end

然后你现在可以使用:

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

请确保您的标题与表格的列名相匹配


最好使用CSV :: Table并使用String.encode(universal_newline: true)。它将CRLF和CR转换为LF


如果要使用SmartCSV

1
2
3
4
5
6
7
8
all_data = SmarterCSV.process(
             params[:file].tempfile,
             {
               :col_sep =>"\t",
               :row_sep =>"
"
             }
           )

这表示每行"\t"中的制表符分隔数据,行由新行"
"
分隔