在Rails中,存储模型中的多个布尔属性的最佳方法是什么?

17

我有一个模型House,其中有许多布尔属性,例如has_fireplacehas_basementhas_garage等等。这些布尔属性大约有30个左右。要使数据库存储和搜索效率更高,应该如何构建这个模型?

例如,我最终希望搜索所有具有壁炉和车库的House

我想单纯地在模型中添加30个布尔属性,每个属性对应数据库中的一列,但我想知道是否有Rails最佳实践,这点让我好奇。

8个回答

16
您的“天真”假设是正确的 - 从查询速度和生产力的角度来看,为每个标志添加一列是最有效的方法。
您可以像其他人描述的那样变得花哨,但除非您正在解决某些非常特定的性能问题,否则这并不值得。最终您会得到一个更难以维护、不太灵活且需要更长时间开发的系统。

2
同意。如果您愿意,将所有布尔值放在属于房子的单独模型中可能会更整洁,例如一个房子具有一个:feature_set(或其他内容),并且功能集具有所有布尔值。然后,您可以使用虚拟属性进行一些简化,例如在House模型中def has_fireplace?; self.feature_set.fireplace; end - John Glass
谢谢你们,Joshua 和 John。John,创建一个单独的 :feature_set 模型来存储所有布尔值是否有很大的优势?你会强烈建议采用单独的模型设计吗?因为如果不是的话,我想我会采用 Joshua 的“向 House 本身添加列”的方法。 - Sanjay
不,除了组织上的原因外,没有真正的理由将事物分开,只是一个选项,如果你发现你的“房子”模型变得太乱了。 - John Glass
天真的假设并不总是正确的。在存储和搜索方面,最有效的结构方式可能是位域。这取决于数据的大小和提出的查询的种类。使用整数列来存储每列最多16个布尔值。2个位字段(整数)列将能够涵盖32个布尔属性。不过我从不超过每列8个属性,所以在这种情况下我会使用4列。参见:http://www.railsbling.com/posts/why-use-flag_shih_tzu/ - Peter H. Boling
从计算的角度来看,肯定有更高效的方法来完成这个任务,但是从开发者的时间利用效率来看,采用一种新颖的方案会更加低效。 - Joshua

7

如果一个模型有很多布尔值,建议使用单个整数和位运算来表示、存储和检索值。例如:

class Model < ActveRecord::Base
  HAS_FIREPLACE = (1 << 0)
  HAS_BASEMENT  = (1 << 1)
  HAS_GARAGE    = (1 << 2)

  ...
end

然后一些名为 flags 的模型属性将被设置如下:

flags |= HAS_FIREPLACE
flags |= (HAS_BASEMENT | HAS_GARAGE)

并且像这样进行测试:

flags & HAS_FIREPLACE
flags & (HAS_BASEMENT | HAS_GARAGE)

你可以将其抽象为方法。作为一种实现方式,应该在时间和空间上非常高效。

5

我建议使用flag_shih_tzu gem。它可以帮助您将许多布尔属性存储在一个整数列中。它为每个属性提供了命名范围,并提供了一种将它们链接在一起作为活动记录关系的方法。


4
这里有另一个解决方案。
你可以创建一个 HouseAttributes 模型,并设置一个双向的 has_and_belongs_to_many 关联。
# house.rb
class House
  has_and_belongs_to_many :house_attributes
end

# house_attribute.rb
class HouseAttribute
  has_and_belongs_to_many :houses
end

那么每个房屋的属性都将成为数据库条目。

不要忘记在您的数据库上设置联接表。


2
过去我选择了这种方法,因为总是有要求(当时或以后)添加更多功能 - 他们迟早会要求 has_solar_panels 和 has_futuretech。 - edralph

3

如果你想要查询这些属性,那么如果性能是一个考虑因素,很遗憾你可能只能使用一流的字段。位字段和标志字符串是解决问题的一种简单方式,但它们与生产数据集不可扩展。

如果你不担心性能问题,那么我建议采用一种实现方式,其中每个属性由一个字符代表(例如“a”=“车库”,“b”=“壁炉”等),并且你只需构建一个表示记录所有标志的字符串。这种方法相对于位字段的主要优点是:a)易于人类调试,b)你不需要担心数据类型的大小。

如果性能是一个问题,那么你可能需要将它们提升为一流的字段。


2

通常我会同意你的幼稚假设是正确的。

如果布尔字段的数量不断增长 (has_fusion_reactor?),您也可以考虑序列化一个标志数组。

# house.rb
class House
  serialize :flagsend

# Setting flags
@house.flags = [:fireplace, :pool, :doghouse]
# Appending
@house.flags << :sauna
#Querying
@house.flags.has_key? :porch
#Searching
House.where "flags LIKE ?", "pool"

1

我在考虑这样的东西

你有一个房屋表(用于存储房屋的详细信息)

你还有另一个主表,名为“特征”(其中包含诸如“壁炉”,“地下室”等功能)

然后你有一个连接表,名为Houses_Features,它有house_id和feature_id

通过这种方式,您可以将功能分配给给定的房屋。不知道这是否符合您的需求,但请考虑一下:D

谢谢和问候

Sameera


0
你可以在一个TEXT列中保存JSON(比如说,data),然后你的查询可以使用SQL的LIKE。
例如:house.data #=> '{"has_fireplace":true,"has_basement":false,"has_garage":true}'
因此,使用 LIKE '%"has_fireplace":true%' 进行查找将返回任何带有壁炉的内容。
在这种情况下,使用模型关系(例如,除了House之外还有Fireplace、Basement和Garage的模型)将非常麻烦,因为你有很多模型。

2
当然,缺点是以通配符开头的LIKE查询无法使用索引,因此如果您在该表中有大量数据,则每个查询都会导致全表扫描。 - Chris Heald

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