Ruby on Rails-从CSV文件导入数据


205

我想将数据从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

谢谢,您能给我一些代码来告诉我执行此操作的最佳方法吗?

Answers:


380
require 'csv'    

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

2
您可以将其放入Rake任务,控制器操作或任何您喜欢的位置...
yfeldblum 2010年

1
效果很好。但是我有一个初学者级的问题-当我尝试浏览Ruby和Rails API文档中描述的方法时,我找不到它们(我查看了Ruby和Rails官方网站的API文档)。例如,我找不到哪个对象返回CSV.parse(),也没有找到to_hash()和with_indifferent_access()方法...也许我看错了地方,或者错过了有关如何遍历Ruby&Rails API的一些基本原则文档。谁能分享阅读Ruby API文档的最佳实践?
弗拉基米尔·克罗兹

2
@daveatflow:是的,请参阅下面的答案,该答案一次将文件读入一行。
Tom Deu Leu 2012年

1
@ lokeshjain2008,它指的是OP的模型。
贾斯汀D.

3
这种方法效率低下!在巨大的CSV文件中,内存使用量激增。下面的一个更好。
unom

206

yfeldblum答案的简单版本,它更简单,并且对于大文件也适用:

require 'csv'    

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

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

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


1
这对于管理大文件来说更好?一次读取一行吗?
2014年

1
@Simon:的确如此。它不会立即将整个文件保留在内存中,而是逐行读取并每行创建一个Molding。
Tom Deu Leu 2014年

我有这个错误,你知道为什么吗? ; categorie; tel'交易
nico_lrx

1
@AlphaNico用您的问题创建一个问题。该错误与此无关,您的Model对象似乎不同步。
unom

在这种情况下,您如何为此编写TestCases?
Afolabi Olaoluwa Akinwumi

11

smarter_csv宝石是专为这个用例发布:读取从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


3
由于包含了CSV类,因此我认为最好使用它,而不是添加或安装其他gem。当然,您没有建议将新的gem添加到应用程序中。添加一系列单独的gem非常容易,每个gem都用于特定目的,并且在您不知道它的应用程序具有过多依赖关系的情况下。(我发现自己有意识地避免添加任何宝石。在我的商店中,我们需要为队友证明添加宝石的理由。)
Tass 2015年

1
@Tass也很容易添加一系列单独的方法,每个方法都有特定的用途,并且在您不了解它之前,您的应用程序就必须维护过多的逻辑。如果一颗宝石可以正常工作,得到很好的维护,并且使用的资源很少,或者可以隔离到相关的环境中(即用于生产任务的暂存),那么在我看来,使用宝石总是一个更好的选择。Ruby和Rails都是为了减少代码编写。
zrisher

我有以下错误,你知道为什么吗?ActiveModel :: UnknownAttributeError:未知属性“警报器; nom_ent;地址; complement_adresse; cp_ville;支付;区域;部门;活动;日期; nb_salaries; nom; prenom;市政; adr_mail; libele_acti; categorie;电话“,用于交易
nico_lrx

我在rake任务上尝试了此操作,控制台返回:rake中止了!NoMethodError:未定义的方法`接近”的零:NilClass stackoverflow.com/questions/42515043/...
马科斯R. Guevara的

1
@Tass将CSV处理分块,提高速度并节省内存可能是添加新gem的一个很好的理由;)
Tilo

5

您可以尝试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。或者,如果存在构成主键的属性的某种组合,则将其用作选择器。不需要索引,它只会使其更快。



2

最好将与数据库相关的进程包装在一个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
...


0

更好的方法是将其包含在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

我知道这是一个老问题,但它仍然出现在Google的前10个链接中。

一行一行地保存行不是很有效,因为这会导致循环中的数据库调用,最好避免这种情况,尤其是当您需要插入大量数据时。

使用批处理插入会更好(并且明显更快)。

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文档以获取详细信息


-2

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


1
您提出的解决方案是什么?
塔斯(Tass)2015年

-3

如果要使用SmartCSV

all_data = SmarterCSV.process(
             params[:file].tempfile, 
             { 
               :col_sep => "\t", 
               :row_sep => "\n" 
             }
           )

这代表每行中制表符分隔的数据,各"\t"行之间用新行分隔"\n"

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.