Ruby on Rails - импорт данных из файла CSV - PullRequest
191 голосов
/ 10 декабря 2010

Я хотел бы импортировать данные из файла 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 ]

361 голосов
/ 10 декабря 2010
require 'csv'    

csv_text = File.read('...')
csv = CSV.parse(csv_text, :headers => true)
csv.each do |row|
  Moulding.create!(row.to_hash)
end
187 голосов
/ 03 мая 2012

Более простая версия ответа yfeldblum, более простая и хорошо работающая также с большими файлами:

require 'csv'    

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

Нет необходимости в with_indifferent_access или symbolize_keys, и нет необходимости сначала читать в файл строку.

Он не хранит весь файл в памяти сразу, а читает построчно и создает Формирование на строку.

10 голосов
/ 15 декабря 2013

гем 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

4 голосов
/ 08 марта 2013

Вы можете попробовать 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 голосов
/ 10 декабря 2010

Это может помочь. Также есть примеры кода:

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

Или для граблей для того же:

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

2 голосов
/ 03 ноября 2016

Лучше обернуть процесс, связанный с базой данных, в блок transaction. Удар по фрагменту кода - это полный процесс заполнения набора языков для модели Language,

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 голосов
/ 31 декабря 2017

Я знаю, что это старый вопрос, но он все еще в первых 10 ссылках в Google.

Не очень эффективно сохранять строки по одной, потому что это вызывает вызов базы данных в цикле, и вам лучше избегать этогоособенно когда вам нужно вставить огромные порции данных.

Лучше (и значительно быстрее) использовать пакетную вставку.

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 (он был впервые выпущен 11 августа 2010 г.), в этом случае просто поместите данные в массив rows и вызовите Model.import rows

для ссылки на gemдокументы для деталей

0 голосов
/ 24 марта 2017

Лучший способ - включить его в задание на грабли.Создайте файл import.rake внутри / lib / tasks / и поместите этот код в этот файл.

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 голосов
/ 28 февраля 2017

Используйте этот камень: https://rubygems.org/gems/active_record_importer

class Moulding < ActiveRecord::Base
  acts_as_importable
end

Тогда вы можете теперь использовать:

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

Просто убедитесь, что ваши заголовки соответствуют именам столбцов вашей таблицы

0 голосов
/ 31 декабря 2014

Лучше использовать CSV :: Table и использовать String.encode(universal_newline: true).Это преобразование CRLF и CR в LF

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...