Rails4:如何在Active record中添加计算列

4
我们在用户表中有一个名为"data"的列,其中包含一个巨大的JSON转储。现在每次我们加载一堆用户时,就会将所有这些数据加载到内存中,导致内存溢出错误。
我们希望能够编写一些计算列,以便在选择语句中使用。
eg:
Instead of doing this 
user.data['profile_data']['data']['image']

We would like to add a column :image and then write a query like:

Here :name and :email are actual columns on the table and :image is a computed column:
Users.where(SOME_CONDITION).select(:name,:email,:image)

主要用例是显示所有用户的索引页面,基本上加载所有用户的数据列。
这将避免在内存中加载巨大的数据列,并帮助我们从数据列中加载所需字段。
如何在Rails4中最好地实现此功能?
更新:
我们在Heroku上使用postgres。

实现方式见此链接:https://dev59.com/V2025IYBdhLWcg3w1JeG#5777262 - Arup Rakshit
这些 :name:email:image 列都只来自于 data 吗? - Arup Rakshit
不,它们不是。更新问题:“这里:name和:email是表上的实际列,而:image是计算列”。 - codeObserver
你使用的是哪个数据库? - Frederick Cheung
3个回答

0

我会把数据列移动到另一个表中,但如果这不是一个选项,请尝试使用lazy_columns gem。

class User < ActiveRecord::Base
  lazy_load :data
end

现在,在初始加载期间将排除data列,但如果您尝试访问.data,它将从数据库中检索。


数据列不会被加载到内存中,这样怎么解决呢? - infused
所以我们在索引视图中显示所有用户。因此,一旦加载该页面,所有数据列都将加载到内存中。我认为使用lazy_load只会延迟加载吗? - codeObserver

0
使用migration添加列:image,然后添加以下代码:

class User < ActiveRecord::Base
  before_save :extract_image

  private

  def extract_image
    self.image = self.data['profile_data']['data']['image']
    self.save
  end
end

before_save: 在 Base.save 之前调用(不管是 create 还是 update save)。


1
我明白了。这个方法可以行得通,但是我不想在实际表中添加新列,因为a]有许多这样的计算字段b]json的模式可能会改变,因此持久化在一个无模式列中。你有什么想法? - codeObserver
这是不好的做法,您不希望将计算字段存储在数据库中。 - ricks
@ricks 好的,你想把它们存储在哪里?为什么将它们存储到数据库中是不好的做法? - Arup Rakshit
@ArupRakshit 这违反了数据库规范化的原则。在处理大型数据集时,一个小错误可能会导致很多数据完整性问题。如果计算被修改,您需要更新表格以重新计算。此外,为计算字段添加更多列可能会降低性能。也许在特定情况下这不是什么大问题,但我仍然认为这是一种不好的做法。 - ricks
1
@ricks 这是一个特定问题的解决方案。只要有测试,就不会出错。对于一列,我想不到一个表……复杂的计算结果可以被缓存,并与所有可用的回调保持同步。Rails 有 counter cache 的原因,如果它被认为是有用的和有帮助的……我也不能反对我的这个答案建议。 - Arup Rakshit

0

Postgresql支持两种json数据类型,如文档所述。

有两种JSON数据类型:json和jsonb。它们接受几乎相同的输入值集合。主要实际区别在于效率。json数据类型存储输入文本的精确副本,处理函数必须在每次执行时重新解析;而jsonb数据以分解的二进制格式存储,由于添加了转换开销,因此稍微慢一些,但处理速度显着更快,因为不需要重新解析。jsonb还支持索引,这可能是一个重要的优势。

因此,要解决您的问题,您需要通过迁移将data列的类型更改为jsonb

# This should use the up and down methods, because change_column 
# is not reversible     
class ChangeUsersDataColumnTypeToJsonb < ActiveRecord::Migration
   def up
      change_column :users, :data, :jsonb
   end
   def down
      change_column :users, :data, :text # or whatever datatype it was
   end
end

与其使用其他方法查询图片字段,不如使用Postgres提供的functions查询json数据类型:

Users.where(SOME_CONDITION).select(:name,:email,"data::json->'image' as image")

然后您可以像访问其他属性一样访问image属性。

您还需要将:data属性定义为延迟加载列,以便在实例化用户对象时不加载该列。

class User < ActiveRecord::Base
  lazy_load :data
end

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