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

คุณกรุณาให้รหัสแก่ฉันเพื่อแสดงวิธีที่ดีที่สุดในการทำเช่นนี้ได้ไหมขอบคุณ

คำตอบ:


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
คุณสามารถวางไว้ในงานคราดหรือในการดำเนินการควบคุมหรือที่ใดก็ได้ที่คุณชอบ ....
yfeldblum

1
มันทำงานได้อย่างสมบูรณ์ อย่างไรก็ตามฉันมีคำถามระดับเริ่มต้น - เมื่อฉันพยายามเรียกดูวิธีการที่อธิบายไว้ในเอกสารประกอบ API ของ Ruby and Rails ฉันไม่สามารถหามันได้ (ฉันดูเว็บไซต์ Ruby และ Rails อย่างเป็นทางการเอกสาร API) เช่นฉันไม่สามารถหาวัตถุที่ส่งคืน CSV.parse () ฉันไม่พบวิธี to_hash () และ with_indifferent_access () ... บางทีฉันดูผิดที่หรือพลาดหลักการพื้นฐานบางประการเกี่ยวกับการท่อง Ruby & Rails API เอกสาร ทุกคนสามารถแบ่งปันวิธีปฏิบัติที่ดีที่สุดในการอ่าน Ruby APIs ได้ไหม
Vladimir Kroz

2
@daveatflow: ใช่ดูคำตอบของฉันด้านล่างซึ่งอ่านในไฟล์ทีละบรรทัด
Tom De Leu

1
@ lokeshjain2008 มันหมายถึงโมเดลของ OP
Justin D.

3
วิธีนี้ไม่มีประสิทธิภาพ! ในไฟล์ CSV ขนาดใหญ่การใช้งาน ram พุ่งสูงขึ้นอย่างรวดเร็ว ด้านล่างดีกว่า
2560

206

คำตอบของ yfeldblum เวอร์ชันที่เรียบง่ายกว่าซึ่งง่ายกว่าและทำงานได้ดีกับไฟล์ขนาดใหญ่:

require 'csv'    

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

ไม่จำเป็นต้อง with_indifferent_access หรือ symbolize_keys และไม่จำเป็นต้องอ่านไฟล์เป็นสตริงก่อน

มันไม่ได้เก็บไฟล์ทั้งหมดไว้ในหน่วยความจำพร้อมกัน แต่อ่านทีละบรรทัดและสร้าง Molding ต่อบรรทัด


1
วิธีนี้จะดีกว่าสำหรับการจัดการไฟล์ขนาดใหญ่ใช่ไหม มันอ่านทีละบรรทัดหรือไม่?
NotSimon

1
@Simon: แน่นอน มันไม่ได้เก็บไฟล์ทั้งหมดไว้ในหน่วยความจำพร้อมกัน แต่อ่านทีละบรรทัดและสร้าง Molding ต่อบรรทัด
Tom De Leu

ฉันมีข้อผิดพลาดนี้คุณรู้หรือไม่ว่าทำไม: ActiveModel :: UnknownAttributeError: แอตทริบิวต์ที่ไม่รู้จัก 'siren; nom_ent; adresse; complement_adresse; cp_ville; จ่ายภูมิภาคภูมิภาคออกเดินทางกิจกรรมวันที่ nb_salaries; นาม; prenom; libele_acti ; categorie; tel 'สำหรับธุรกรรม
nico_lrx

1
@AlphaNico สร้างคำถามพร้อมปัญหาของคุณ ข้อผิดพลาดนั้นไม่เกี่ยวข้องกับสิ่งนี้วัตถุโมเดลของคุณดูเหมือนจะไม่ซิงค์กัน
2560

ในกรณีนี้คุณจะเขียน 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 ฉันรู้สึกดีกว่าที่จะใช้แทนที่จะเพิ่มหรือติดตั้งอัญมณีเพิ่มเติม ได้รับคุณไม่ได้เสนอให้เพิ่มอัญมณีใหม่ในแอปพลิเคชัน มันง่ายมากที่จะเพิ่มชุดของอัญมณีแต่ละอันสำหรับวัตถุประสงค์เฉพาะและก่อนที่คุณจะรู้ว่าใบสมัครของคุณมีการพึ่งพามากเกินไป (ฉันพบว่าตัวเองมีสติหลีกเลี่ยงการเพิ่มอัญมณีใด ๆ ในร้านของฉันเราจำเป็นต้องแสดงให้เห็นถึงการเพิ่มไปยังเพื่อนร่วมทีมของเรา.)
Tass

1
@Tass มันค่อนข้างง่ายที่จะเพิ่มชุดของวิธีการแต่ละวิธีสำหรับวัตถุประสงค์เฉพาะและก่อนที่คุณจะรู้ว่าใบสมัครของคุณมีตรรกะมากเกินไปที่คุณต้องรักษาไว้ หากอัญมณีทำงานได้รับการดูแลอย่างดีและใช้ทรัพยากรน้อยหรือสามารถกักกันสภาพแวดล้อมที่เกี่ยวข้อง (เช่นการจัดเตรียมสำหรับงานการผลิต) ดูเหมือนว่าฉันจะเป็นตัวเลือกที่ดีกว่าในการใช้อัญมณี Ruby และ Rails ล้วนเกี่ยวกับการเขียนโค้ดให้น้อยลง
zrisher

ฉันมีข้อผิดพลาดต่อไปนี้คุณรู้ไหมว่าเพราะอะไร ActiveModel :: UnknownAttributeError: ไม่ทราบแอตทริบิวต์ 'ไซเรน; nom_ent; adresse; complement_adresse; cp_ville; จ่าย; ภูมิภาค Departement; Activite; วัน; nb_salaries; นาม; prenom; civilité; adr_mail; libele_acti; ประเภท; Tel' สำหรับการทำธุรกรรม
nico_lrx

ฉันลองสิ่งนี้ในภารกิจ rake คืนคอนโซล: rake ถูกยกเลิก! NoMethodError: วิธีที่ไม่ได้กำหนด `ปิด 'สำหรับศูนย์: NilClass stackoverflow.com/questions/42515043//
Marcos R. Guevara

1
@Tass chunking ประมวลผล CSV, การปรับปรุงความเร็วและประหยัดหน่วยความจำอาจจะมีเหตุผลที่ดีสำหรับการเพิ่มอัญมณีใหม่;)
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หากเป็นสิ่งที่คุณต้องการคุณยังอาจพิจารณาได้รับการกำจัดของคีย์หลักเพิ่มโดยอัตโนมัติจากตารางและการตั้งค่าคีย์หลักเพื่อ อีกทางเลือกหนึ่งหากมีการรวมกันของแอตทริบิวต์ที่เป็นคีย์หลักใช้เป็นตัวเลือก ไม่จำเป็นต้องมีดัชนีมันจะทำให้เร็วขึ้น


4

สิ่งนี้สามารถช่วย มันมีตัวอย่างโค้ดด้วย:

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

หรือสำหรับงานที่เสาะหาเพื่อทำสิ่งเดียวกัน:

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


erikonrails.snowedin.net/?p=212 หักโปรดฉันเปิดปัญหาสำหรับการทำกับ rake task ที่นี่stackoverflow.com/questions/42515043//
Marcos R. Guevara

2

มันจะดีกว่าที่จะห่อกระบวนการที่เกี่ยวข้องกับฐานข้อมูลภายในtransactionบล็อก Code snippet เป็นกระบวนการเต็มรูปแบบของการสร้างชุดของภาษาในรูปแบบภาษา

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

ใช้อัญมณีนี้: https://rubygems.org/gems/active_record_importer

class Moulding < ActiveRecord::Base
  acts_as_importable
end

จากนั้นคุณสามารถใช้:

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

เพียงให้แน่ใจว่าส่วนหัวของคุณตรงกับชื่อคอลัมน์ของตารางของคุณ


0

วิธีที่ดีกว่าคือรวมไว้ในภารกิจคราด สร้างไฟล์ import.rake ภายใน / lib / task / และใส่รหัสนี้ลงในไฟล์นั้น

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

ฉันรู้ว่ามันเป็นคำถามเก่า แต่ก็ยังอยู่ในลิงค์ 10 อันดับแรกใน google

ไม่มีประสิทธิภาพในการบันทึกแถวหนึ่งต่อหนึ่งเพราะจะทำให้การเรียกฐานข้อมูลในลูปและคุณหลีกเลี่ยงได้ดียิ่งขึ้นโดยเฉพาะเมื่อคุณต้องการแทรกส่วนข้อมูลขนาดใหญ่

จะดีกว่า (และเร็วกว่ามาก) ในการใช้การแทรกแบบกลุ่ม

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

คุณสามารถสร้างแบบสอบถามดังกล่าวด้วยตนเองและทำกว่าModel.connection.execute(RAW SQL STRING)(ไม่แนะนำ) หรือใช้อัญมณีactiverecord-import(มันถูกเปิดตัวครั้งแรกเมื่อวันที่ 11 สิงหาคม 2010) ในกรณีนี้เพียงแค่ใส่ข้อมูลในอาร์เรย์rowsและโทรModel.import rows

อ้างถึงเอกสารอัญมณีสำหรับรายละเอียด


-2

มันเป็นเรื่องดีที่จะใช้ CSV :: String.encode(universal_newline: true)ตารางและการใช้งาน มันแปลง CRLF และ CR เป็น LF


1
ทางออกที่คุณเสนอคืออะไร?
Tass

-3

หากคุณต้องการใช้ SmartCSV

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

สิ่งนี้แสดงถึงข้อมูลที่คั่นด้วยแท็บในแต่ละแถว"\t"โดยมีแถวคั่นด้วยบรรทัดใหม่"\n"

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.