1. 程式人生 > >如何設計動態(不定)欄位的產品資料庫表?--淘寶多產品屬性欄位設計方法

如何設計動態(不定)欄位的產品資料庫表?--淘寶多產品屬性欄位設計方法

看到szsm部落格,覺得他分析的很不錯,這裡把他的資料整理一下

---------------------------------------------------------------------------------------------------------------------------------

專案組會議上討論的關於不定欄位數目的資料庫表問題並沒有結果,今天繼續分析之後發現問題可能還更大。當時討論的結果是可能採用四種技術:

  • 動態增加資料庫表字段
  • 預留足夠的空白欄位,執行時作動態影射
  • 用xml格式儲存在單欄位裡
  • 改列為行,用另外一個表存放定製欄位
現在我們來分析一下四種技術的優劣,不過首先可以排除的是第一點動態增加欄位的方法,因為在實際操作時候幾乎是不可能的(sqlserver太慢,oracle索性不支援),基本可以不討論就排除。剩下後三點。

先來討論預留空白欄位的方法,基本原理就是在資料庫表設計的時候加入一些多餘的欄位,看下面的程式碼:

CREATE TABLE Sample(
  name varchar(12),
  field0 varchar(1),
  field1 varchar(1),
  fieldN varchar(1)
}

然後看實際執行時候的需要,動態分配欄位給系統使用,也許需要一個這樣的結構來描述分配情況:

public class Available
{
  public int CurrentUnusedFieldNumber;
  public Hashtable FieldToRealName;
}

也許某一時刻的資料狀況是這樣的: CurrentUnusedFieldNumber=3, 哈西表FieldToRealName包含內容是("field0"="SomeId", "field1"="AnyName", "field2=IsOk")
現在的問題是如果要配合Hibernate,如何來處理?以上段的資料使用狀況為例子,如果我們的類定義是這樣:
public class Entity01
{
  public string Name;
  public string SomeId;
  public string AnyName;
  public bool IsOk;
}

也許只需要修改一下xxx.hbm.xml,把 SomeId 和 field0 做成對應就ok了。但是在執行時我們怎麼知道會有這樣的類定義?除非我們做動態程式碼生成,自動編譯也許可以,但是問題也許就到其他方面去了;如果我們不用動態定義,那麼類就只能是這樣:
public class Entity01
{
  public string Name;
  public Hashtable ExtraFieldAndValues;
}


使用的時候,用 entity01.ExtraFieldAndValues.setValue("AnyName", "boss") 的方式來引用,也許這樣是修改最少的了,但是問題是Hibernate不支援這樣的方法。
再來討論單欄位儲存的方法,我們使用這樣的資料庫表定義
CREATE TABLE Sample
(
  Name varchar(12),
  Xml CLOB(102400) // 僅作說明而已
)


然後對應這樣的類定義
public class Entity01
{
  public string Name;
  public string Xml;
  public Hashtable ExtraNameAndValueFromXml;
}


我們的程式碼就可以這樣使用:string id = entity01.ExtraNameAndValueFromXml.getValue("SomeId") 了。這樣解決看起來很不錯,不僅不需要Available表,而且看起來Hibernate對它的支援也很完美,但是致命的問題在於:如果保持高效的查詢?除非資料庫系統本身對此有支援,否則就只能用低效的substring或者like做查詢,這在大批量資料中根本就不可行。
是不是折衷一下,把兩種方法的優點和起來?問題有來了:怎麼保持兩者之間資料的同步?難道要我們用儲存過程去解析xml內容?
所以,一個兩難的問題,需要我們認真去解決。我們通過認真的需求分析,也許可以減少可變欄位的數量,但是隻要有一個可變欄位或者可變的可能性存在,我們始終要去解決這個兩難的問題。
期待繼續討論。

(新增部分)


還有一種方法就是改列為行,用另外一個表存放擴充套件欄位,定義可以如下:
CREATE TABLE SampleFields
(
  idSample Integer,
  fieldName varchar(30),
  fieldValue varchar(100)
)
其中idSample關聯到Sample表的id欄位(我沒有寫出來)。這樣的話,Hibernate很容易支援,也可以支援Sql的查詢,而且可以支援把內容放到Hashtable中去,看起來是目前最好的方式了。但是在大容量資料的時候,SampleFields表的資料會是主表資料量的N倍(看定製的欄位數目多少而定),同樣存在有很嚴重的效能問題。


哪位高人還能再給一個方案?

---------------------------------------------------------------------------------------------------------------------------------

--------2005-7-22新增-----------
很多朋友給出了很好的建議,其中蛙蛙池塘 給出了一個表結構,因為看起來不甚方便,我把類圖畫出來如下:

圖所表示的內容簡單來說是這樣:
1。一個很寬的表ProductAttributeValues,包含用到的幾乎所有可能的型別的值,但是每次只能用一個型別的值
2。將可變的列轉為行,存放到上表中
3。為了存放型別定義和一些下拉列表的內容,設計了ProductAttributeTemplates和關聯的其他表
4。ProductListItems中存放Product中所有項的說明和順序。

這種思路其實就是把產品的“知識級”(設計模式用語)和“操作級”都表現出來了,如果要劃分,則圖的左上角三個表屬於“操作級”,其餘的屬於“知識級”。wljcan 網友建議我去看《設計模式》的“觀察模式”,我發現上圖其實就是一種和“觀察模式”相似的設計。《設計模式》看了很久都沒能看下去,不過這幾天正在看“觀察模式”,等有心得了,再來看看能不能對上圖的結構修改一下。

怡紅公子 提醒說oracle和sql server對xml欄位其實已經不錯了,所以找了一下,但是真的是既產品中恐怕還是不敢用,不知道效能如何。雖然採用xml方式是我最推崇的方法。而且一旦採用xml方式儲存,恐怕就會有[url=]changyu[/url] 網友提醒的資料型別問題,要存一個圖片的話恐怕就歇菜了(當然也不是說xml中就一定不能存圖片)。

不過我現在覺得xml欄位還是要的,作為輔助儲存手段,因為畢竟未來查詢起來可能會更方便些,然後結合“觀察模式”也就是類似上圖的方法作為主要的儲存手段,雖然有一些冗餘,結合我的使用 NVelocity 解析 PowerDesigner 的cdm檔案 ,工作量也不會太大。

CREATE TABLE [dbo].[ProductAttributeDataTypes] (
[DataTypeId] [int] NOT NULL ,
[Description] [varchar] (30) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL 


CREATE TABLE [dbo].[ProductAttributeListItems] (
[LookupListId] [int] NOT NULL ,
[ListItemId] [int] IDENTITY (1, 1) NOT NULL ,
[DisplayName] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL 
)
CREATE TABLE [dbo].[ProductAttributeLookupLists] (
[LookupListId] [int] IDENTITY (1, 1) NOT NULL ,
[Name] [varchar] (80) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL 

CREATE TABLE [dbo].[ProductAttributeTemplateCategories] (
[PATCategoryId] [int] IDENTITY (1, 1) NOT NULL ,
[Name] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
[DisplayName] [varchar] (255) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL 
)
CREATE TABLE [dbo].[ProductAttributeTemplates] (
[AttributeTemplateId] [int] IDENTITY (1, 1) NOT NULL ,
[Name] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
[DataType] [int] NOT NULL ,
[LookupListId] [int] NULL ,
[PATCategoryId] [int] NOT NULL ,
[CustomerHelp] [varchar] (4000) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
[DisplayName] [varchar] (255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL 

CREATE TABLE [dbo].[ProductAttributeValues] (
[ProductId] [int] NOT NULL ,
[AttributeTemplateId] [int] NOT NULL ,
[valueInt] [int] NULL ,
[valueStr] [varchar] (255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
[valueMemo] [text] COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
[valueBool] [bit] NULL 
)
CREATE TABLE [dbo].[ProductListItems] (
[ProductListId] [int] NOT NULL ,
[ProductId] [int] NOT NULL ,
[ItemDescription] [varchar] (80) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
[DisplaySequence] [int] NOT NULL 
)

不知道大家有沒有看懂這些表之間的關係,這是一個產品目錄的表,產品有不確定的屬性,每個屬性對應的資料型別也不一樣,而且有的是用下拉列表選擇的,有的是 是或否,有的是一段兒文字,這裡用的就是資料模板來做的,這是<asp.net電子商務高階程式設計>裡的一部分資料庫

---------------------------------------------------------------------------------------------------------------------------------

以前有這樣的一個需求,不考慮像京東或者淘寶這樣分類下有子分類的情況,只考慮一層分類的情況下,可以隨便新增分類,可以任意給商品新增屬性,而不需要更改表的結構. 於是設計了一個這樣的結構,實現還是可以實現,一直在用,但是在操作上比較麻煩,大家討論下有沒有更好的方式. 

----------------------------- 以下是幾個關聯的物件 省去了 getter/setter 方法 ------------------ 
Field.java 屬性 

Java程式碼

  • public class Field{  
  •    private Integer id;  
  •    private String name; // 屬性名稱
  • }  


ProductField.java 商品屬性 

Java程式碼

  • public class ProductField{  
  •      private Integer id;  
  •      private Field field; // 屬性物件
  •       private String value; // 屬性值 這裡的值不管是整數 小數 還是什麼型別都是用 String 型別
  • }  



Product.java 商品表 

Java程式碼

  • public class Product{  
  •    private Integer id;  
  •    private String name; //商品名稱
  •    //根據商品 載入這個商品的所有屬性 和屬性值
  •    private Set<ProductField> productFields = new HashSet<ProductField>();//商品屬性集合
  • }  



Type.java 

Java程式碼

  • public class Type{  
  •    private Integer id;  
  •    private String name; //分類名稱
  •    //通過型別 可以載入這個型別下的所有商品  一種型別對應多種商品
  •    //(暫時不考慮一種商品屬於多種型別 例如手機屬於電器類,也屬於通訊類產品)
  •    private Set<Product> products = new HashSet<Product>();  
  • }  


----------------------  Hibernate 配置檔案就不帖出來了,以下是由POJO和hbm配置檔案生成出來的表 示例- 


//表關係 
Type       
id name    
1  電腦      
3  手機       
4  腦殘        

Product 
id  name  typeid 
1   SONY    1(fk)  
2   DELL    1 
3   韓國豬   4 
4   叄欣     3 

Fields    
id name   
1  顏色 
2  重量 
3  型號 
... 
ProductFields 
id pdctid  fieldid   value 
1  (fk)1   (fk1)1    紅色 
2      1        2    500g 
3      2        3    XYZ 
4      2        1    白色 
5      3        4    1000.00 

--載入的測試資料 

  • +++++++++ 商品資訊 ++++++++  
  • Name:Sony  
  • 價格 - 2000.00
  • 型號 - YY-1939
  • 名稱 - 鎖你牌手機  



我要補充的是,當商品越來越多的時候,屬性的增加也是自然的,有肯能屬性增加到1000種, 
那麼我在後臺管理 新增加一個商品,給它新增屬性時,這時就需要從已經存在的屬性中選出 
它所需要的那麼10種或者20種屬性。 

我的想法是,程式處理中: 
1.在頁面輸出可選擇的屬性時按屬性名稱排序,這樣可能選擇快一些。 
2.提供屬性搜尋,看資料庫中有沒有這麼一屬性,如果有直接使用,如果沒有就新增 
3.修改此資料庫表,屬性應該和大的型別相關聯,作為常用屬性。 次要屬性可以從後續的屬性列表中選擇。 



動態列效能上確實可能會有點問題。比如你要統計:紅色的手機的一個月銷售額就需要至少在四個表(型別、產品、屬性、銷售)進行聯合查詢。 

我以前遇到類似問題,而且時間上不允許更改設計了,當時只是採取了一些簡單的處理方式,比如針對需要的統計生成中間表,對庫進行索引優化,根據時間進行分表等等。不過我個人感覺出問題的地方往往是一些特定統計,如果你能確定或基本上確定最終這些表的統計查詢的方式,我覺得問題不大。你可以預估下資料量,假設半年後的運營, 型別、產品、屬性、銷售等資料大概是多少,聯合查詢的總記錄數是多少。如果這個資料比較恐怖,那你再可以考慮更換其他方式。

複雜商品的分類,類似淘寶的分類 
1.每類商品有無限級分類 
2.每個商品可能會有交叉分類 
3.每類商品的擴充套件屬性不一樣 
比如: 
夾克的擴充套件屬性為 
款式: 拉鍊夾克 風格: 休閒 品牌: other/其它 適合季節: 春秋 尺碼: M L 顏色: 其它顏色 質地: 純棉 
主機板的擴充套件屬性為 
品牌: 微星/MSI 型別: Socket478 晶片組: Intel 845 平臺型別: Intel平臺 寶貝成色: 8成新 

這些擴充套件屬性都會動態的變化 

那麼問題來了: 
1.全文搜尋如何合理建立? 
2.可能後臺擴充套件屬性表是否需要動態建立? 
3.如果單件商品屬於交叉分類的話,查詢結果記錄重複是否需要? 
4.高效的無限級分類演算法大家可否指點一二,這個困惑了我很久了? 

不求完整解決,給個思路也成


每個商品都是一個xml檔案 每個xml檔案有一個ID 每個xml檔案可以採用這樣的結構 
<xml> 
<名稱/> 
<屬性/> 
<屬性/> 
...... 
</xml> 

當檢索商品的時候 比如用Socket478晶片組檢索 就檢索出所有含有Socket478晶片組屬性節點的xml檔案. 用一個xql語句就可以搞定. 返回的結果是xml檔案 可以用xsl進行處理 然後用css顯示



這類問題(無限級別分類,可以交叉分類)很難。
正是現代 tag,分類時代的熱點問題。我也考慮調查了很久。
如果解決得好,就可以被收購了。

xml 解決起來確實比 relation table 容易一些。xml全文檢索也比較容易做。
我想,winterwolf會跳出來,終於等到了。

不過,xml也有一些限制。如果能有一種專門描述這類問題的資料結構就好了。
我想到過幾種資料結構。不過都沒有想透。
Multiple Key Hashmap。多維陣列。等。

xml確實是一種解決的辦法,tianxinet的子表方法有點繁瑣了,擴充套件屬性有很多表也帶來了維護的困難性了。。如果像淘寶的商品分類那樣的話,你的子表數量是很驚人的。。。。 
繼續關注中

---------------------------------------------------------------------------------------------------------------------------------

前幾天有人問了我一個這樣的問題,因為時間的關係,我當時嘗試做了幾種回答,比如將產品先分大類,為每個大類設計一個產品表,在產品表中包括該類的基本屬性,並預留一些欄位作為擴充套件屬性,對於同一大類不同的產品,考慮增加擴充套件表。不過這個答案似乎沒有得到認可。認真一想也是,如果這樣,表改有多少,查詢結構又該多複雜。
      電子商務,尤其是B2B和B2C的電子商務平臺,有著自己的特殊情況的。首先,它的產品及其廣泛,差不多就涵蓋了世間所有可以出售的商品,其次,每種商品的屬性共性也不太可能分析和抽取。
      今天在網上發現一個網友的blog(http://hi.baidu.com/ifos/blog/item/5cf3de1f03dd7b67f724e4ea.html),他提出了四種設計思路,

方案一。

就一個產品表 product,然後這個表裡包括所有的產品屬性,每個屬性用一個欄位表示。

方案二。

還是隻用一個產品表 product 。
與方案一不同的是,私有屬性設定為一個欄位 Private_Attribute ,
然後每個產品的多個私有屬性都放這個欄位裡,並且用一個分隔符號隔開
比如書籍,就是 它在 Private_Attribute 欄位裡 的表示就是 :

出版社||||作者||||出版日期

方案三;

產品表 + 私有屬性表 + 私有屬性值 表
產品表 裡 就包括一些產品的公共屬性
私有屬性表 裡 設定私有屬性的名稱 ,比如出版社 、作者 、出版日期
私有屬性值 表 裡就是 每個產品 私有屬性的值

例如:
產品表: 
product_id = 1 ; product_name =《ajax實踐》
私有屬性表:
Attribute_id = 1 ; Attribute_name = 出版社
Attribute_id = 2 ; Attribute_name = 作者

私有屬性值表:
id = 1 ; product_id = 1 ; Attribute_id = 1 ; Attribute_value = 清華出版社
id = 2 ; product_id = 1 ; Attribute_id = 2 ; Attribute_value = 老外

方案四;

每個不同型別的產品單獨設計一個數據庫,比如一個書籍的資料表 product_book,一個MP3的資料表 product_mp3

        看了一下,突然萌生出一些想法來,願與大家交流討論。
       首先想的是,電子商務產品表設計的最佳是由哪些因素決定的。個人認為,主要包括高效率的查詢效能以及可易擴充套件的設計。我們於是從這兩個方面分析上述四種設計,第一種方案几乎沒有可擴充套件性(列的擴充套件遠遠不夠於包含所有產品不同的屬性);第二個方案看上去可擴充套件性不錯,不過它的屬性就全部以純文字的樣式儲存,查詢效率自然想到差;第三種方案看上去是一個折中,實際上它是產品、屬性、屬性值的笛卡爾積了,資料量將非常巨大,根本不適合大型的電子商務平臺,因為查詢效率會很低,並且對於結果的拼排將是很大的代銷;第四種方案也許擁有最好的擴充套件性,但是如果對於跨產品的查詢,也將是低效率的。
       這麼看來,這將是個NP了。而實際上呢?阿里巴巴做得很好。我不知道阿里巴巴是如何做到的,但是在仔細看了阿里巴巴的網站後,個人覺得有些東西其實妨礙了我們的思路。
        列下幾個問題,可供大家思考:
     1. 是不是產品的所有可能屬性都屬於查詢條件? 看看阿里巴巴,它的查詢條件涵蓋所有屬性了麼?
     2. 是不是所有屬性都是具有獨立性的存在的?換句話說,如果一個物品有幾百上千種可列屬性,是否我們需要將它每一個屬性作為單獨存在的屬性來描述?
     3. 除了傳統的資料庫的SQL查詢,我們又是否可以藉助某些資料庫的特性或者說其他的查詢技術呢?(比如XPATH)。
     4. 客戶只輸入了一個模糊條件,是不是就一定意味著需要在所有的產品資訊中查詢呢?(是否可以識別使用者習慣,是否又可以像傳統搜尋引擎一樣進行關鍵字排行呢?)
     5. 使用者輸入的關鍵字查詢,又是不是對產品的所有屬性有效呢?還是隻是涵蓋了產品的關鍵屬性?
     6. 客戶的查詢真的是像傳統資訊系統一樣知道精確的所有結果麼?還是隻想知道最佳的結果?
      其實有興趣的朋友,可以上淘寶、上阿里巴巴,看一看,也就可以給這幾個問題列出答案了。

      好了,雖然我不是電子商務行業,也沒有真實面對過這類問題,談到這裡,就大概說一下我對電子商務平臺產品物流模型設計的思路吧,有興趣的朋友可以參考驗證一下,也可以交流一下。
1. 對產品按照大類進行區分,每個大類有一個產品表。產品表有該大類產品最基本的關鍵屬性資訊(應該控制在十個以內,這些屬性,也被用作關鍵字查詢索引),而接下來,又有一些預定義的擴充套件屬性(命名如customized1, customized2,等等),這些擴充套件屬性有一個重要的前提,就是它們的屬性值都是列表選擇的,而不是自由輸入的(當然這些選擇項是可以在另外的表中定義的),這些屬性是用作在詳細的高階查詢中使用的,使用選擇而不是自由輸入第一是可以提高索引效率,第二是可以避免因為字元差錯引起的歧義,第三是可以使用區間值。每個不同的產品小類有不同的customized定義。最後有再有幾個欄位,則存放其他的由客戶特性帶來的屬性值,但是是以xml的形式存放,可以方便使用XPATH來提升效率。
      當用戶在統一的文字框輸入模糊查詢條件時,可以通過事先建立的關鍵字索引或者優先排名或者使用者習慣來確定首先尋找的產品大類範圍。這樣就將查詢首先限制在幾個主要的產品中。然後通過基本的屬性進行基於SQL的比較查詢。同時列出查詢結果所對應的產品小類。使用者可以進入產品小類進行更詳細的查詢,這個時候的查詢就包括了由customized欄位定義的一些區間值或者選項值。使用者同時又可以輸入一些特別的條件(在這個區間外,也許某類產品本身的特性,如同pconline上不同產品的詳細特性),這個時候就可以運用XML技術來進行最後一些欄位的過濾與篩選了。
    其實整個這個設計思路是在確保客戶的查詢有效性、響應時間及資料庫效能及結構的可擴充套件性上進行了一些折中和考慮,在不損失表結構的可擴充套件性上,既保證了每張表的表空間不會過大,也同時保證了查詢的最優有效性。它其實還需要藉助其他的一些技術,比如搜尋關鍵字識別及優化、結果排名、使用者習慣及模糊行為分析、基於XML的查詢等。
     蠻想聽聽來自網際網路電子商務行業的朋友的一些想法,歡迎交流討論。

http://raylinn.javaeye.com/admin/blogs/260407

ms 和我以前想的這個有點像。。。 固定的屬性和模糊的介紹都通過lunce來搜尋。。




問題補充
我也是想用多個表,多個表有點不好的地方,就是如果使用者在A表買了一件產品,又在B表買了一件產品,那麼到最後統計使用者買過的產品時,雖然可以從下訂單那個表讀出來,但是感覺有點亂~~~
問題補充:
產品類別表(類別id,名稱,...) 
產品表(產品id,名稱,規格,...) 
產品屬性表(產品屬性id,產品id,屬性id,值,...) 

這種方法行不行?
問題補充:
產品表(產品id,名稱,規格,...) 
產品屬性表(產品屬性id,產品id,屬性id,值,...) 

就說這兩個表吧,當插入一件產品的時候,屬性應該是直接去到產品表的,如果加了一個屬性表,那麼插入的時候, 
難道可以插入一個產品id在產品表,然後又插入屬性在產品屬性表嗎?

方案一(推薦):在商品型別已知的情況下,為每類商品定製一個類。class 手機,class 筆記本 
方案二:如果商品型別位置,那就用一個通用的商品類,裡面放個property

方案一:商品屬性定義表(存放xml格式定義),商品表(基本欄位+屬性定義ID+屬性xml描述),有了xml格式和xml描述,其他的xml資料解析和前臺展示就可通用處理了,細節你可以再看看。
方案二:如上。 
只是開啟想了想,不知道能否幫到你