Ruby on Rails - CSV dosyasından Veri Aktar


205

Bir CSV dosyasından mevcut bir veritabanı tablosuna veri aktarmak istiyorum. CSV dosyasını kaydetmek istemiyorum, sadece verileri alın ve mevcut tabloya koyun. Ruby 1.9.2 ve Rails 3 kullanıyorum.

Bu benim masam:

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

Bana bunu yapmanın en iyi yolunu göstermek için bir kod verebilir misin, teşekkürler.

Yanıtlar:


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
Bir Komisyon görevine ya da bir denetleyici eylemine ya da istediğiniz herhangi bir yere
koyabilirsiniz

1
Mükemmel çalıştı. Ancak başlangıç ​​düzeyinde bir sorum var - Ruby ve Rails API belgelerinde açıklanan yöntemleri taramaya çalıştığımda bunları yerinde bulamadım (resmi Ruby ve Rails sitelerine, API belgelerine baktım). Örneğin, hangi nesnenin CSV.parse () döndürdüğünü bulamadım, to_hash () ve with_indifferent_access () yöntemlerini bulamadım ... Belki de yanlış yere baktım ya da Ruby & Rails API'sini geçme konusunda bazı temel prensipleri kaçırdım docs. Herkes Ruby API belgelerini okuma konusunda en iyi uygulamayı paylaşabilir mi?
Vladimir Kroz

2
@daveatflow: evet, aşağıdaki dosyada her seferinde bir satır okuyan cevabım.
Tom De Leu

1
@ lokeshjain2008, OP modelini ifade eder.
Justin

3
Bu yöntem verimsiz! Büyük CSV dosyalarında ram kullanımı fırlar. Aşağıdaki daha iyi.
unom

206

Yfeldblum'un cevabının daha basit versiyonu, daha basit ve büyük dosyalarla da iyi çalışıyor:

require 'csv'    

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

With_indifferent_access veya symbolize_keys gerekmez ve önce dosyada bir dizeye okunmaya gerek yoktur.

Tüm dosyayı bir kerede bellekte tutmaz, satır satır okur ve satır başına bir Kalıp oluşturur.


1
Bu büyük dosya boyutlarını yönetmek için daha iyi değil mi? Her seferinde bir satırda mı okuyor?
NotSimon

1
@Simon: gerçekten. Tüm dosyayı bir kerede bellekte tutmaz, satır satır okur ve satır başına bir Kalıp oluşturur.
Tom De Leu

Bu hatayı aldım, neden biliyor musunuz ?: ActiveModel :: UnknownAttributeError: bilinmeyen özellik 'siren; nom_ent; adresse; complement_adresse; cp_ville; öder; bölge; departman; activite; tarih; nb_salaries; nom; prenom; civilite; adr_mail; libele_acti ; categorie; İşlem için tel '
nico_lrx

1
@AlphaNico Sorununuzla ilgili bir soru oluşturun. Bu hata bununla alakasız, Model nesneleriniz senkronize değil gibi görünüyor.
unom

Bu durumda, bunun için TestCases'i nasıl yazarsınız?
Afolabi Olaoluwa Akinwumi

11

smarter_csvTaş özellikle bu kullanım örneği için yaratıldı: CSV dosyasından verileri okumak ve hızlı bir veritabanına kayıt oluşturulması.

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

Seçeneğini kullanabilirsiniz chunk_sizeBir kerede N csv-satırlarını okuma ve ardından yeni döngüleri oluşturmak yerine hemen oluşturacak işler oluşturmak için iç döngüde Resque'i kullanabilirsiniz - bu şekilde, girdi oluşturma yükünü yayabilirsiniz birden fazla işçiye.

Ayrıca bakınız: https://github.com/tilo/smarter_csv


3
CSV sınıfı dahil edildiğinde, ek bir mücevher eklemek veya eklemek yerine onu kullanmanın daha iyi olduğunu hissediyorum. Verilmiş, uygulamaya yeni bir mücevher eklenmesini önermiyordunuz. Her biri belirli bir amaç için bir dizi bireysel mücevher eklemek çok kolaydır ve bunu bilmeden önce uygulamanızın aşırı bağımlılıkları vardır. (Kendimi bilinçli olarak herhangi bir mücevher eklemekten kaçınırım.
Dükkânımda

1
@Tass, her biri belirli bir amaç için bir dizi bireysel yöntem eklemek de oldukça kolaydır ve bunu bilmeden önce uygulamanızın sürdürmeniz gereken aşırı mantık vardır. Bir mücevher işe yararsa, bakımlıysa ve çok az kaynak kullanıyorsa veya ilgili ortamlara (örneğin üretim görevleri için hazırlama) karantinaya alınabiliyorsa, gem'i kullanmak için her zaman daha iyi bir seçenek gibi görünüyor . Ruby ve Rails daha az kod yazmakla ilgilidir.
zrisher

Aşağıdaki hatayla karşılaştım, nedenini biliyor musunuz? ActiveModel :: UnknownAttributeError: bilinmeyen nitelik 'siren; nom_ent; adresse; complement_adresse; cp_ville; öder; bölgesini; DEPARTEMENT; activité; tarihi; nb_salaries; nom; prénom; civilite; adr_mail; libele_acti; Kategori; tel' İşlem için
nico_lrx

Bunu bir komisyon görevinde denedim, konsol geri dönüyor: komisyon iptal edildi! NoMethodError: nil için tanımsız yöntem `` close '': NilClass stackoverflow.com/questions/42515043/…
Marcos R. Guevara

1
@ CSV işlemesini parçalamak, hızı iyileştirmek ve bellek tasarrufu yapmak yeni bir mücevher eklemek için iyi bir gerekçe olabilir;)
Tilo

5

Deneyebilirsiniz 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

İstediğiniz buysa, tablodan otomatik artan birincil anahtardan kurtulmayı ve birincil anahtarı olarak ayarlamayı da düşünebilirsiniz name. Alternatif olarak, birincil anahtarı oluşturan özelliklerin bir birleşimi varsa, bunu seçici olarak kullanın. Endeks gerekmez, sadece daha hızlı yapar.



2

Veritabanı ile ilgili süreci bir transactionblok içine sarmak daha iyidir . Kod pasajı darbesi, dil modeline bir dizi dil tohumlama işleminin tam bir işlemidir,

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

Aşağıdaki pasaj languages.csvdosyanın bir kısmıdır ,

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


0

Daha iyi bir yol, bir komisyon görevine dahil etmektir. / Lib / görevleri / içinde import.rake dosyası oluşturun ve bu kodu bu dosyaya yerleştirin.

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

Bundan sonra terminalinizde bu komutu çalıştırın rake csv_model_import[file.csv,Name_of_the_Model]


0

Eski bir soru olduğunu biliyorum ama yine de Google'da ilk 10 bağlantıda.

Satırları birer birer kaydetmek çok verimli değildir, çünkü döngüde veritabanı çağrısına neden olur ve özellikle büyük veri bölümleri eklemeniz gerektiğinde bundan daha iyi kaçınabilirsiniz.

Toplu kesici uç kullanmak daha iyidir (ve önemli ölçüde daha hızlıdır).

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

Bu tür bir sorguyu manuel olarak oluşturabilir ve daha sonra Model.connection.execute(RAW SQL STRING)(tavsiye edilmez) veya gem'i kullanabilirsiniz activerecord-import(ilk olarak 11 Ağu 2010'da yayınlanmıştır), bu durumda sadece dizi rowsve çağrıya veri koyunModel.import rows

ayrıntılar için değerli belgelere bakın


-2

CSV :: Table kullanmak ve kullanmak daha iyidir String.encode(universal_newline: true). CRLF ve CR'yi LF'ye dönüştürür


1
Önerdiğiniz çözüm nedir?
Tass

-3

SmartCSV Kullanmak İstiyorsanız

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

Bu, her satırdaki sekmeyle ayrılmış verileri "\t"yeni satırlarla ayrılmış satırları temsil eder"\n"

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.