2010-12-10 105 views
161

Je souhaite importer des données d'un fichier CSV dans une table de base de données existante. Je ne veux pas enregistrer le fichier CSV, il suffit de prendre les données et de les mettre dans la table existante. J'utilise Ruby 1.9.2 et Rails 3.Ruby on Rails - Importer des données à partir d'un fichier CSV

Ceci est ma table:

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 

Pouvez-vous me donner un code pour me montrer la meilleure façon de ce faire, merci.

Répondre

303
require 'csv'  

csv_text = File.read('...') 
csv = CSV.parse(csv_text, :headers => true) 
csv.each do |row| 
    Moulding.create!(row.to_hash) 
end 
+3

Pouvez-vous me montrer où mettre ce code, s'il vous plaît. – freshest

+1

Vous pouvez le mettre dans une tâche Rake, ou dans une action du contrôleur, ou n'importe où vous voulez .... – yfeldblum

+1

Cela a fonctionné parfaitement. Cependant, j'ai une question de niveau débutant - lorsque j'ai essayé de parcourir les méthodes décrites dans la documentation de l'API Ruby and Rails, je n'ai pas pu les trouver sur place (j'ai consulté les sites officiels de Ruby and Rails). E.g. Je n'ai pas pu trouver quel objet renvoie CSV.parse(), je n'ai pas trouvé les méthodes to_hash() et with_indifferent_access() ... Peut-être ai-je cherché au mauvais endroit ou manqué un principe de base sur la façon de parcourir Ruby & Rails Documents de l'API Quelqu'un peut-il partager la meilleure pratique comment lire les docs API Ruby? –

4

Cela peut aider. Il a des exemples de code aussi:

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

Ou pour une tâche de coupe pour faire la même chose:

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

+0

http://erikonrails.snowedin.net/?p=212 est cassé, s'il vous plaît, je l'ai ouvert un problème pour faire avec la tâche de râteau ici http://stackoverflow.com/questions/42515 043/impossible-de-courir-rake-tâche-avec-smarter-csv –

167

version simplifiée de la réponse de yfeldblum, qui est plus simple et fonctionne bien aussi avec de gros fichiers:

require 'csv'  

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

Non need for_indifferent_access ou symbolize_keys, et pas besoin de lire d'abord le fichier dans une chaîne.

Il ne conserve pas tout le fichier en mémoire à la fois, mais lit ligne par ligne et crée un moulage par ligne.

+1

C'est mieux pour gérer les grandes tailles de fichiers, non? Est-ce qu'il lit dans une ligne à la fois? – Simon

+1

@Simon: en effet. Il ne garde pas le fichier entier en mémoire à la fois, mais lit ligne par ligne et crée un moulage par ligne. –

+0

J'ai cette erreur, savez-vous pourquoi?: ActiveModel :: UnknownAttributeError: attribut inconnu 'siren; nom_ent; adresse; complément_adresse; cp_ville; pay; région; département; activite; date; nb_salaries; nom; prenom; civilite; adr_mail; libele_acti; categorie; tel 'pour la transaction – AlphaNico

4

Vous pouvez essayer 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 

Si c'est ce que vous voulez, vous pouvez également envisager de se débarrasser de la clé primaire auto-augmentation de la table et le réglage de la clé primaire à name. Sinon, s'il existe une combinaison d'attributs formant une clé primaire, utilisez-la comme sélecteur. Aucun index n'est nécessaire, il va juste le rendre plus rapide.

10

La gemme smarter_csv a été spécialement créée pour cette utilisation: lire les données du fichier CSV et créer rapidement des entrées de base de données.

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

Vous pouvez utiliser l'option chunk_size à lire N csv lignes à la fois, puis utiliser Resque dans la boucle intérieure pour créer des emplois qui permettra de créer les nouveaux enregistrements, plutôt que de les créer tout de suite - de cette façon vous pouvez répartir la charge de générer des entrées à plusieurs travailleurs.

Voir aussi: https://github.com/tilo/smarter_csv

+1

Comme la classe CSV est incluse, je pense qu'il est préférable de l'utiliser au lieu d'ajouter ou d'installer une gemme supplémentaire. Certes, vous n'avez pas proposé qu'une nouvelle gemme soit ajoutée à l'application. Il est si facile d'ajouter une série de gemmes individuelles, chacune dans un but précis et avant que vous le sachiez, votre application a des dépendances excessives. (Je me suis consciemment évité d'ajouter des gemmes dans mon magasin, nous devons justifier l'addition à nos coéquipiers.) – Tass

+0

Works! Je vous remercie! –

+1

@Tass il est également assez facile d'ajouter une série de méthodes individuelles, chacune pour un but spécifique et avant que vous le sachiez, votre application a une logique excessive que vous devez maintenir. Si une gemme fonctionne, est bien entretenue et utilise peu de ressources ou peut être mise en quarantaine dans les environnements pertinents (c'est-à-dire Staging pour les tâches de production), il me semble * toujours * une meilleure option pour utiliser la gemme. Ruby et Rails sont tous sur l'écriture de moins de code. – zrisher

-3

Si vous souhaitez utiliser SmartCSV

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

Cela représente onglet données délimitées dans chaque rangée "\t" avec des rangées séparées par de nouvelles lignes "\n"

-2

Il est préférable d'utiliser CSV :: Table et utilisation . Il convertit CRLF et CR en LF

+1

Quelle est votre solution proposée? – Tass

1

Il est préférable d'enrouler le processus lié à la base de données dans un bloc transaction.extrait de code coup est un processus complet d'ensemencement d'un ensemble de langues à modèle de langage,

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 

Snippet ci-dessous est une vue partielle du fichier languages.csv,

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

Utilisez ce petit bijou: https://rubygems.org/gems/active_record_importer

class Moulding < ActiveRecord::Base 
    acts_as_importable 
end 

Ensuite, vous pouvez maintenant utiliser:

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

Assurez-vous simplement que vos en-têtes correspondent aux noms de colonnes de votre table

0

La meilleure façon est de l'inclure dans une tâche de râteau. Créez le fichier import.rake dans/lib/tasks/et placez ce code dans ce fichier.

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 

Après cette exécution de cette commande dans votre terminal rake csv_model_import[file.csv,Name_of_the_Model]

0

Je sais qu'il est vieille question, mais il reste dans les 10 premiers liens dans google.

Il n'est pas très efficace d'enregistrer les lignes une à une car cela provoque un appel de base de données dans la boucle et vous éviterez mieux cela, en particulier lorsque vous devez insérer d'énormes portions de données.

Il est préférable (et beaucoup plus rapide) d'utiliser un encart batch.

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

Vous pouvez construire une telle requête manuellement et que faire Model.connection.execute(RAW SQL STRING) (non recommandé) ou utiliser gemme activerecord-import (il a été libéré le 11 août 2010) dans ce cas il suffit de mettre les données dans le tableau rows et appelez Model.import rows

refer to gem docs for details