本文出處:http://www.cnblogs.com/wy123/p/6677073.html
在做數據統計類數據庫設計的時候,在考慮數據存儲的時候,經常會遇到邏輯上同一個BusinessID對應多個數據點的情況,
比如工資表中的員工ID以及各項工資信息,財務表中的各個報表Id和多個數據點之間的信息
面對這種情況,如何來設計表結構,是橫表,還是豎表,各有那些優缺點,本文將做一個粗淺的分析。
橫標和豎表的表現形式
日常生活中也有很多類似的例子,先用一個Excel畫一個例子,比如工資表
這么做就是“橫表”,特點是,一個ID對應所有的值信息,以行Key-Value1-Value2-Value3的方式存儲

如下是豎表(縱表),特點是每行僅存儲該ID的某一個類別字段的值,以行的方式存儲Key-Value的方式存儲

橫標和豎表的設計示例
下面通過一個具體的例子來說明橫標和豎表的一些特點
--橫標 CREATE TABLE HorizontalTable ( Id int identity(1,1), BusinessId varchar(50) , CategoryVal1 varchar(20) , CategoryVal2 decimal(20,5) , CategoryVal3 datetime , CategoryVal4 varchar(20) , CategoryVal5 varchar(20) , CategoryVal6 varchar(20) ) insert into HorizontalTable values ('BH000001','value1',89.12,'20170406','abc4','abc5','abc6') insert into HorizontalTable values ('BH000002','value2',99.11,'20170407','abc4','abc5','abc6') --豎表 CREATE TABLE VerticalTable ( Id int identity(1,1), BusinessId varchar(50), CategoryKey varchar(20), Val varchar(20) ) insert into VerticalTable values ('BH000001','CategoryKey1','values1') insert into VerticalTable values ('BH000001','CategoryKey2',89.12) insert into VerticalTable values ('BH000001','CategoryKey3','20170406') insert into VerticalTable values ('BH000001','CategoryKey4','abc4') insert into VerticalTable values ('BH000001','CategoryKey5','ab5') insert into VerticalTable values ('BH000001','CategoryKey6','ab6') insert into VerticalTable values ('BH000002','CategoryKey1','values2') insert into VerticalTable values ('BH000002','CategoryKey2',99.12) insert into VerticalTable values ('BH000002','CategoryKey3','20170407') insert into VerticalTable values ('BH000002','CategoryKey4','abc4') insert into VerticalTable values ('BH000002','CategoryKey5','abc5') insert into VerticalTable values ('BH000002','CategoryKey6','abc6')
橫表中的數據:

豎表中的數據

可能實際應用中,要比這個示例中的情況更加復雜,那么在設計表結構的時候,如何選擇橫標或者豎表?
首先來看橫標的特點
對於橫表
1,同一個Key值對應的列是固定的,比如,比如HorizontalTable中有6個字段
2,各個字段的值是自由的,比如HorizontalTable中的CategoryVal1是varchar類型的,CategoryVal2是decimal的
3,表中並不存儲描述性字段本身(相比縱表)
4,相比豎表,存儲同樣多的數據,行數要少
對於豎表
1,同一個Key值對應的列是動態的,因為是按照行存儲的,可以存儲成Key1—Value1,Key1—Value2,Key1—Value3的方式存儲
2,字段的類型是固定的,但是類似是要兼容的,不能有個性化的字段,比如VerticalTable中的CategoryKey+Val,因為固定了這么一個字段
3,表中需要存儲描述字段本身(相比橫標),要根據BusinessKey值的不同,重復存儲CategoryKey
4,相比橫表,存儲同樣多的數據,行數要多
綜上可以看出,
橫標的優點:橫標的有點事顯示的較為清晰直觀,同時在字段的選擇上更為科學合理,具體的字段可以根據具體情況划分字段類型,
橫標的缺點:不方便擴展和公用,也就是說設計了一張橫標,只能在固定的某一種特定的相對不變的場景下使用,
比如加字段,或者類似的業務想公用一張橫表,都有局限
豎表的優點:最大的特點是可以靈活擴展存儲的內容,同時具有一定的公用性
因為豎表的存儲結構不受字段個數的限制,可以存儲具有一定共性的業務數據。
豎表的缺點:豎表的字段類型要兼容,比如橫標可以根據具體的值設計成varchar,decimal,datetime等,
橫標為了兼容以上字段類型,只能設計成varchar的,可能會浪費一定的空間
橫標和豎表主要考慮的是擴展性和共同性,對於顯示方式問題,個人認為倒是問題不大,無非是行轉列和列轉行的問題
如下是一個將上述設計的橫表轉豎表和豎表轉橫標的示例,也不復雜,因此說,顯示的問題不是大問題
select * from HorizontalTable --列轉行 ;WITH HorizontalCET AS ( SELECT Id,BusinessId,CategoryVal1, cast(CategoryVal2 as varchar(20)) as CategoryVal2, cast(CategoryVal3 as varchar(20)) as CategoryVal3, CategoryVal4, CategoryVal5 FROM HorizontalTable ) SELECT Id,BusinessId,ColumnName,ColumnVal FROM HorizontalCET UNPIVOT (ColumnVal FOR ColumnName IN (CategoryVal1, CategoryVal2, CategoryVal3, CategoryVal4, CategoryVal5) ) tmp --列轉行 select * from VerticalTable SELECT * FROM ( select BusinessId , CategoryKey, Val from VerticalTable )t PIVOT( MIN(Val) FOR CategoryKey IN (CategoryKey1, CategoryKey2, CategoryKey3, CategoryKey4, CategoryKey5, CategoryKey6) )a


關於橫表和豎表的性能問題
關於性能問題,很難一概而論,還要結合具體的情況作分析,比如查詢方式,查詢數據了,索引結構等等都有一定的關系。
表面上看,豎表存儲了大量冗余的數據,浪費了一定量的磁盤空間是事實,但是極端情況下橫表也有可能造成極大的空間浪費
了解SQL Server的同學肯定知道,
SQL Server中正常來來說是行存儲,一行數據不能跨頁存儲(當然forwarded存儲方式的數據除外,有機會說這個),
SQL Server的最小存儲單位是頁(Page),一個頁的大小是8kb,除去page信息固定占用的空間之外是8060個字節,
每一行固定的一行數據除了數據自身占用的空間外,至少(不是一定,表結構越復雜占用的額外空間越大)還要占用1+1+2+2+1=7個字節

對於寬表,一旦字段長度達到一定的程度,
比如每行長度為800個字節,理論上將,在一個page上,存儲9行記錄之后,還剩余800字節的空間(具體剩余多少跟表結構有關,這里只是舉例說明),
對不起,第十行數據來了已經存不進去了,只能新開頁面分配存儲空間,這樣,當前這個頁面就浪費了800字節的存儲空間
反觀豎表,因為存儲的數據行都非常短,即便發生上述情況,也只會浪費很少的一點數據空間(小於一行數據的空間)
極端情況下會更加有意思,參考這個http://www.cnblogs.com/studyzy/archive/2008/11/27/1342003.html

從讀取的另外角度來看,大多數情況下,建表的方式都是行存儲,意味着每一行的數據是存儲在一起的(字段大的時候當然可以跨頁面),單個頁面存儲的數據行數就變得較少
sqlserver讀取數據的時候是按照行來讀取的,不管你查詢幾個字段,最終都是要將整個行的數據讀取出來,
而存儲的最小單元是頁,也就是page,
如果一個表的字段非常多,那么一次查詢,即便是需要這些字段中的一部分,也要將所有的字段讀取出來,這意味着,你讀取的行數多的話,讀取出來的不需要的字段也將變得更多
比如一個表中設計了30個字段,正常情況下,一個查詢只需要讀取6個字段,即便是這樣,sqlserver在執行查詢的時候依然讀取的是30個字段出來,
這樣的的話,讀取同樣多的數據,就可能需要讀取更多的頁才能完成這個查詢
如果是設計成豎表,根據具體的Id來,如果有合理的索引,使用索引就可以完成查詢,而不需要再去讀表的page,這樣就可以避免橫表讀取時候的這種情況。
有上述可見,對於橫表和豎表,不管是設計上還是存儲上,優點和缺點都是看站在哪個角度來看的,
從一個角度來看是有點,從另外一個角度看就可能會變成缺點,只有舍棄一部分,根據實際情況權衡之后做出取舍。
凡事無絕對,適合即可。
總結:
本文從適應場景、存儲、性能等方面粗淺第分析了表設計時候橫標和豎表的特點和優缺點,
具體設計的時候可綜合考慮,做出合理的選擇。
另外,本文肯定還有沒有預計或者說想到的情況以及評估方向,也希望有想法的同學補充,謝謝。
