2017-04-24 50 views
0

我有一个家长ProductCategory和子女Product。例如:如何有效地搜索字符串数据?

ProductCategory --- Product 
      Drill --- DeWalt DWD 112 
       --- Black & Decker 5 C 
     Bicycle --- Motobecane Turino ELITE Disc Brake 
       --- Shimano Aluminum 

对于给定的ProductCategory,有一组属性的所有Products应与彼此可比较(即,对数据)。但是,这组属性可能会在ProductCategories

之间变化。例如,对于Drill的ProductCategory,属性可能为Voltage, Amps, Corded vs Cordless。每个Product钻取需要有这样的信息。但是,对于自行车的ProductCategory,属性应为Size, Road vs Mountain,并且每辆Product自行车都需要具有此信息。 (对不起,我不知道任何关于钻子或自行车...为什么我选择这是愚蠢的)

我试图设计的数据库,如果给定Product,属性是我可以很容易搜索。例如,理想情况下,我可以运行此命令:

drills = Product.where(product_category_id:1) 
drills.where("voltage >= ?", 5) 
-> returns the individual drills, which may include DeWalt but not Black & Decker 

这似乎提出了一个有趣的权衡......因为那时我得有Product有列,每ProductCategory每个属性,即使是那些与它无关。例如:

# Product columns 
:voltage, :integer #for Drill 
:amps, :integer #for Drill 
:corded, :boolean #for Drill 
:size, :integer #for Bicycle 
:mountain, :boolean #for Bicycle 
... 

这似乎不是可持续的......你可以看到非常快,只需几ProductCategories很快就会有Product列无限多的!

在光谱的另一端,我想是每一个Product的所需属性定义在父ProductCategory,然后请求这些属性/上Product存储它们作为一个字符串化数据:

# ProductCategory has a column... 
:required_attributes, :text 

ProductCategory.where(name:"Drill").first.required_attributes 
-> "voltage,amps,corded" 

ProductCategory.where(name:"Bicycle").first.required_attributes 
-> "size,mountain" 

# Product has a column... 
:attribute_data, :text 

Product.where(name:"DeWalt").first.attribute_data 
-> "{'voltage':5,'amps':5,'corded':5}" 

随着在上面的设计中,我可以创建一个前端,在创建Product时,必须为每个required_attributes在根据逗号分割后提供信息。但是,当然,这使得搜索效率低得多,至少我认为它......所以这是我的问题。 如何有效地搜索串化数据?如果我正在搜索至少5伏的所有演习,请完成以下步骤。

drills = ProductCategory.where(name:"Drill") 
drills.where("attribute_data ...") 
+0

查看更新的答案。 –

+0

我删除了我的答案,因为这不是一个很好的答案。但是,这不是一个真正可以在SO上回答的问题,因为它非常广泛,并且需要关于现有模式(如EAV表和JSON列)的一些知识。 – max

+0

我得到@max,但我发现其中的参考有用,我正在探索这个公认的广泛主题。即使作为评论,你是否介意重新发布? – james

回答

0

最简单的办法就是使用JSON or HSTORE数据类型列上products存储规格。

但是,如果你想有更多的控制和验证按规格,你可以使用一个设计,一个连接表:

class Product 
    has_many :specs 
    has_many :definitions, through: :specs 
end 

# This is the "normalized" table that defines an specification 
# for example "Size". This just holds the name for example and the acceptable values. 
class Definition 
    has_many :specs 
    has_many :products, through: :specs 
end 

# this contains the actual specs of a product (the value) 
# specs.payload is a JSON column 
class Spec 
    belongs_to :definition 
    belongs_to :product 
    delegate :name, to: :definition 

    def value 
    payload[:value] 
    end 

    def value=(val) 
    payload[:value] = val 
    end 
end 

一个经典的问题,使用这样的设计将是, specs表必须将该值存储为文本(或varchar)列并处理类型转换的问题。但大多数现代数据库支持动态列类型,如HSTORE或JSON,您可以使用它们来存储实际值。

的缺点是,你必须使用一个特殊的SQL语法查询时:

Spec.where("payload->>'value' = ?", "foo") 

这是一种对所谓的Entity–attribute–value model归一化的变化可以是一个反模式,但往往是只有关系数据库中的动态属性才是最好的解决方案。

参见:

+0

确实有帮助。对于我正在做的事情,最终使用JSON数据类型,也因为我一直想进入NoSQL一段时间。不要删除这些答案!将来可能会想要阅读 – james

0

另一种方式来处理这避免了EAV表的问题,这个问题是使用多表继承。这个例子使用ActiveRecord :: ActsAs。

class Product < ActiveRecord::Base 
    actable 
    belongs_to :brand 
    validates_numericality_of :universal_product_code, length: 12 
    validates_presence_of :model_name, :price 
end 

class Bicycle < ActiveRecord::Base 
    acts_as :product 
    validates_numericality_of :gears 
    validates_presence_of :size 
end 

class PowerTool < ActiveRecord::Base 
    acts_as :product 
    validates_numericality_of :voltage, :amps 
    validates_presence_of :voltage, :amps 
end 

这将存储在一个products表基本信息:

change_table :products do |t| 
    t.decimal :price 
    t.sting :model_name 
    t.integer :universal_product_code, length: 12 
    t.integer :actable_id 
    t.string :actable_type 
end 

而且它使用多态关联到产品的亚型存储更具体的表:

create_table :bicycles do |t| 
    t.integer :gears 
    t.integer :size 
end 

create_table :power_tools do |t| 
    t.boolean :corded 
    t.integer :amps 
    t.integer :size 
end 

的这里的优点是你有一个定义的模式,而不是一堆松散的属性。 缺点是如果你正在设计一个通用的网上商店,那么一个固定的模式不会削减它。