首页 文章

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

提问于
浏览
177

我想将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

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

11 回答

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

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

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

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

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

  • 0

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

    另见:https://github.com/tilo/smarter_csv

  • 1

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

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

    或者执行相同的rake任务:

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

  • -3

    您可以尝试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

    最好将数据库相关进程包装在 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
    ...
    
  • 4

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

    class Moulding < ActiveRecord::Base
      acts_as_importable
    end
    

    然后你现在可以使用:

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

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

  • 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]

  • 179

    我知道这是一个古老的问题,但它仍然在谷歌的前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

    refer to gem docs for details

  • 0

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

  • 10

    如果要使用SmartCSV

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

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

相关问题