如何将csv文件中的部分列插入到表格中

3

我有一个大型的.csv文件,其中包括以下标题行:

:headers => 
     ["_id_", "name", "ascii_names", "alternate_name", "latitute",
     "longitude", "feature_class", "feature_code", "country_code", "cc2", 
     "admin_code_1", "admin_code_2", "admin_code_3", "admin_code_4", "population", 
     "elevation", "dem", "timezone", "modification_date"]}

我有一个只包含name, country_code, timezone列的表。

我想仅将.csv文件中这三列插入到表中。

我尝试过以下方法:

CSV.foreach(csv_file, {:col_sep => "\t", :quote_char => '&', :write_headers => true, :headers => ["_id_", "name", "ascii_names", "alternate_name", "latitute", "longitude", "feature_class", "feature_code", "country_code", "cc2", "admin_code_1", "admin_code_2", "admin_code_3", "admin_code_4", "population", "elevation", "dem", "timezone", "modification_date"]}
    ) do |row|
    City.create row.to_hash.values_at(:name, :timezone, :country_code)
    binding.pry
  end

问题在于,运行后检查表时,没有任何值被填充。行本身已经创建,但它们都是空的。
如何正确映射这些内容,让.create()知道哪些列与表相匹配?

我建议将文件加载到一个包含所有列(作为字符串)和所有行的表中。然后,从暂存表插入到最终表中。 - Gordon Linoff
这似乎非常低效,而且处理方式完全间接。为什么你会推荐这样做呢? - Cole Bittel
因为它可以让您最好地控制可能发生的任何错误。插入文本文件中的数据时,错误并不罕见,处理这些错误的方法比带入数据的一些小效率更重要。 - Gordon Linoff
1
@GordonLinoff:一般情况下我同意你的观点(或者如果这是我的应用和数据库),但我们在谈论Rails,这意味着所有列可能都允许为空,数据库中可能没有维护数据一致性和正确性的内容,可能存在大量的数据逻辑在数据库之外,并且可能没有办法检查数据库中的任何内容,因为所有这些都应该由Ruby代码处理。好消息是,Rails4终于发现了外键,因此有一些希望。 - mu is too short
2个回答

3
您需要先确定您将把列标题识别为字符串还是符号。在您的headers => ["_id_" ...]中,您将它们声明为Strings,但在row.to_hash.values_at(:name, ...)中,您正在寻找Symbols。-感谢@mu。
其次,您可以手动指定要插入表中的值,而无需对它们进行哈希处理,就像通过Ruby创建新条目时一样。
City.create(:name => row[:name], :timezone => row[:timezone], :country_code => row[:country_code])

总的来说,你的方法应该像这样:

  CSV.foreach(csv_file, {:col_sep => "\t", :quote_char => '&', #:write_headers => true, 
    :headers => [:geonameid, :name, :ascii_names, :alternate_name, :latitute, :longitude, :feature_class, :feature_code, :country_code, :cc2, :admin_code_1, :admin_code_2, :admin_code_3, :admin_code_4, :population, :elevation, :dem, :timezone, :modification_date]}
    ) do |row|
    City.create(:name => row[:name], :timezone => row[:timezone], :country_code => row[:country_code])
  end

1
首先,您需要告诉CSV文件头是字符串类型:
:headers => ["_id_", "name", ...]

这意味着在 CSV.foreach 块内部,这样做:
row.to_hash

这是一个具有字符串键的Hash。然后在该Hash上调用values_at,并要求三个Symbol键,因为具有字符串键的Hash不会对Symbol键提供任何值,所以values_at正确地给出了[nil]。这意味着您的CSV.foreach块只是一种过度复杂的表达方式:

City.create [nil]

当你将一个Array传递给create时,实质上是在说:
array.map { |e| City.create(e) }

这将你的代码块缩小为:
City.create nil

在ActiveRecord或ActiveModel中,它可能会对传递的属性调用to_h,因此使用nil调用create与使用空Hash调用是相同的。所有这一切的结果是一堆空记录,因为这就是您要求ActiveRecord执行的操作,并且您没有阻止它将垃圾数据写入数据库。 解决方案有两个部分: 1.在任何地方都使用字符串键或者在任何地方都使用符号键。 2.create在您的情况下需要一个散列参数,因此请使用Hash#slice而不是values_at。 像这样:
CSV.foreach(csv_file, ...) do |row|
  City.create row.to_hash.slice(*%w[name timezone country_code])
end

PS:您真的应该在数据库中包含一些NOT NULL约束,并在模型中进行一些验证。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接