Ruby on Rails - Nhập dữ liệu từ tệp CSV


205

Tôi muốn nhập dữ liệu từ tệp CSV vào bảng cơ sở dữ liệu hiện có. Tôi không muốn lưu tệp CSV, chỉ cần lấy dữ liệu từ nó và đặt nó vào bảng hiện có. Tôi đang sử dụng Ruby 1.9.2 và Rails 3.

Đây là bảng của tôi:

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

Bạn có thể cho tôi một số mã để chỉ cho tôi cách tốt nhất để làm điều này, cảm ơn.

Câu trả lời:


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
Bạn có thể đặt nó trong tác vụ Rake, hoặc trong hành động của bộ điều khiển hoặc bất cứ nơi nào bạn thích ....
yfeldblum

1
Nó hoạt động hoàn hảo. Tuy nhiên tôi có một câu hỏi ở cấp độ mới bắt đầu - khi tôi cố duyệt các phương thức được mô tả trong tài liệu API của Ruby và Rails, tôi không thể tìm thấy chúng tại chỗ (Tôi đã tìm trên các trang web chính thức của Ruby và Rails, các tài liệu API). Ví dụ: tôi không thể tìm thấy đối tượng nào trả về CSV.parse (), tôi đã không tìm thấy các phương thức to_hash () và with_indifferent_access () ... Có thể tôi đã tìm sai vị trí hoặc bỏ lỡ một số nguyên tắc cơ bản về cách vượt qua API Ruby & Rails tài liệu. Bất cứ ai cũng có thể chia sẻ cách thực hành tốt nhất để đọc tài liệu API của Ruby?
Vladimir Kroz

2
@daveatflow: có, xem câu trả lời của tôi dưới đây, mỗi lần đọc trong tệp một dòng.
Tom De Leu

1
@ lokeshjain2008, nó đề cập đến mô hình của OP.
Justin D.

3
Phương pháp này không hiệu quả! Trên các tệp CSV lớn, các skyrockets sử dụng ram. cái dưới đây là tốt hơn
unom

206

Phiên bản đơn giản hơn của câu trả lời của yfeldblum, đơn giản hơn và cũng hoạt động tốt với các tệp lớn:

require 'csv'    

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

Không cần with_indifferent_access hoặc Symbolize_keys và trước tiên không cần đọc trong tệp thành một chuỗi.

Nó không giữ toàn bộ tập tin trong bộ nhớ cùng một lúc, nhưng đọc theo từng dòng và tạo ra một khuôn trên mỗi dòng.


1
Điều này là tốt hơn để quản lý kích thước tập tin lớn phải không? Nó đọc trong một dòng tại một thời điểm?
NotSimon

1
@Simon: thực sự. Nó không giữ toàn bộ tập tin trong bộ nhớ cùng một lúc, nhưng đọc theo từng dòng và tạo ra một khuôn trên mỗi dòng.
Tom De Leu

Tôi có lỗi này, bạn có biết tại sao không ?: ActiveModel :: UnknownAttributionError: unknown property 'siren; nom_ent; adresse; bổ sung ; phân loại; tel 'cho giao dịch
nico_lrx

1
@AlphaNico Tạo một câu hỏi với vấn đề của bạn. Lỗi đó không liên quan đến điều này, các đối tượng Mô hình của bạn dường như không đồng bộ.
unom

Trong trường hợp này, làm thế nào để bạn viết TestCase cho việc này?
Afolabi Olaoluwa Akinwumi

11

Các smarter_csvđá quý đã được tạo ra đặc biệt cho use-case này: để đọc dữ liệu từ file CSV và nhanh chóng tạo các mục cơ sở dữ liệu.

  require 'smarter_csv'
  options = {}
  SmarterCSV.process('input_file.csv', options) do |chunk|
    chunk.each do |data_hash|
      Moulding.create!( data_hash )
    end
  end

Bạn có thể sử dụng tùy chọn chunk_sizeđể đọc N csv-rows cùng một lúc, sau đó sử dụng Resque trong vòng lặp bên trong để tạo các công việc sẽ tạo các bản ghi mới, thay vì tạo chúng ngay lập tức - bằng cách này bạn có thể truyền tải tải của các mục nhập cho nhiều công nhân.

Xem thêm: https://github.com/tilo/smarter_csv


3
Khi bao gồm lớp CSV, tôi cảm thấy tốt hơn khi sử dụng nó thay vì thêm hoặc cài đặt một viên ngọc bổ sung. Cấp, bạn đã không đề xuất rằng một viên ngọc mới được thêm vào ứng dụng. Thật dễ dàng để thêm một loạt các viên đá quý riêng lẻ, mỗi loại cho một mục đích cụ thể và trước khi bạn biết nó, ứng dụng của bạn có sự phụ thuộc quá mức. (Tôi thấy mình có ý thức tránh việc bổ sung bất kỳ viên đá quý nào. Trong cửa hàng của tôi, chúng tôi cần biện minh cho việc bổ sung cho đồng đội của mình.)
Tass

1
@Tass cũng khá dễ dàng để thêm một loạt các phương thức riêng lẻ, mỗi phương thức cho một mục đích cụ thể và trước khi bạn biết nó, ứng dụng của bạn có logic quá mức mà bạn phải duy trì. Nếu một viên đá quý hoạt động, được bảo trì tốt và sử dụng ít tài nguyên hoặc có thể được cách ly với các môi trường có liên quan (ví dụ: Phân đoạn cho các nhiệm vụ sản xuất) thì dường như tôi luôn là một lựa chọn tốt hơn để sử dụng đá quý. Ruby và Rails đều viết về việc viết ít mã hơn.
zrisher

Tôi có lỗi sau, bạn có biết tại sao không? ActiveModel :: UnknownAttributeError: unknown thuộc tính 'còi; nom_ent; adresse; complement_adresse; cp_ville; trả; khu vực; Lãnh thổ hải; activite; ngày; nb_salaries; nom; prenom; civilite; adr_mail; libele_acti; mục; tel' cho giao dịch
nico_lrx

Tôi đã thử điều này trong một nhiệm vụ cào, giao diện điều khiển trở lại: cào bỏ! NoMethodError: phương thức không xác định `close 'cho nil: NilClass stackoverflow.com/questions/42515043/
Marcos R. Guevara

1
@Tass kiểm tra quá trình xử lý CSV, cải thiện tốc độ và tiết kiệm bộ nhớ có thể là một lý do hợp lý để thêm một viên ngọc mới;)
Tilo

5

Bạn có thể thử 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

Nếu đây là những gì bạn muốn, bạn cũng có thể xem xét loại bỏ khóa chính tăng tự động khỏi bảng và đặt khóa chính thành name. Ngoài ra, nếu có một số kết hợp các thuộc tính tạo thành khóa chính, hãy sử dụng nó làm bộ chọn. Không có chỉ số là cần thiết, nó sẽ chỉ làm cho nó nhanh hơn.



2

Nó là tốt hơn để bọc quá trình cơ sở dữ liệu liên quan trong một transactionkhối. Đoạn mã là một quá trình đầy đủ để gieo một tập hợp các ngôn ngữ vào mô hình Ngôn ngữ,

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

Đoạn bên dưới là một phần của languages.csvtệp,

aa,Afar
ab,Abkhazian
af,Afrikaans
ak,Akan
am,Amharic
ar,Arabic
as,Assamese
ay,Aymara
az,Azerbaijani
ba,Bashkir
...

0

Sử dụng đá quý này: https://rubygems.org/gems/active_record_importer

class Moulding < ActiveRecord::Base
  acts_as_importable
end

Sau đó, bạn có thể sử dụng:

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

Chỉ cần chắc chắn rằng các tiêu đề của bạn khớp với tên cột của bảng của bạn


0

Cách tốt hơn là đưa nó vào một nhiệm vụ cào. Tạo tệp import.rake bên trong / lib / task / và đặt mã này vào tệp đó.

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

Sau đó chạy lệnh này trong thiết bị đầu cuối của bạn rake csv_model_import[file.csv,Name_of_the_Model]


0

Tôi biết đó là câu hỏi cũ nhưng nó vẫn nằm trong 10 liên kết đầu tiên trong google.

Việc lưu từng hàng một không hiệu quả lắm vì nó gây ra cuộc gọi cơ sở dữ liệu trong vòng lặp và tốt hơn hết bạn nên tránh điều đó, đặc biệt là khi bạn cần chèn một phần dữ liệu khổng lồ.

Tốt hơn (và nhanh hơn đáng kể) để sử dụng chèn hàng loạt.

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

Bạn có thể xây dựng một truy vấn như vậy một cách thủ công và hơn là làm Model.connection.execute(RAW SQL STRING)(không được đề xuất) hoặc sử dụng đá quý activerecord-import(lần đầu tiên được phát hành vào ngày 11 tháng 8 năm 2010) trong trường hợp này chỉ cần đặt dữ liệu vào mảng rowsvà gọiModel.import rows

tham khảo tài liệu đá quý để biết chi tiết


-2

Tốt hơn là sử dụng CSV :: Bảng và sử dụng String.encode(universal_newline: true). Nó chuyển đổi CRLF và CR thành LF


1
Giải pháp đề xuất của bạn là gì?
Tass

-3

Nếu bạn muốn sử dụng SmartCSV

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

Điều này thể hiện dữ liệu được phân tách bằng tab trong mỗi hàng "\t"với các hàng được phân tách bằng các dòng mới"\n"

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.