概念介紹
橫表概念
橫表就是普通的建表方式,每一個字段代表一個KPI指標。舉個列子,一個學生的成績表:學號、數學成績、語文成績、英語成績、
物理成績、化學成績......如下所示:
SQL> DESC STUDENT_SCORE Name Type Nullable Default Comments ---------------- ---------- -------- ------- -------- STUDENT_NO NUMBER(10) 學號 CHINESE_SCORE NUMBER Y 語文成績 ENGLISH_SCORE NUMBER Y 英語成績 MATH_SOCRE NUMBER Y 數學成績 PHYSICAL_SCORE NUMBER Y 物理成績 SPORTS_SCORE NUMBER Y 體育成績 CHEMICAL_SCORE NUMBER Y 化學成績 BIOLOGICAL_SCORE NUMBER Y 生物成績
豎表概念
SQL> DESC STUDENT_SOCRES; Name Type Nullable Default Comments ------------ ------------ -------- ------- -------- STUDENT_NO NUMBER(10) Y 學號 SUBJECT_CODE VARCHAR2(12) Y 科目編碼 SUBJECT_NAME VARCHAR2(12) Y 科目名稱 SCORES NUMBER Y 成績
優劣比較
指標項目 |
橫表 |
豎表 |
可擴展性 |
差 |
強 |
性能方面 |
優於豎表 |
比橫表差 |
業務描述 |
好 |
差 |
代碼復雜 |
代碼簡單 |
代碼復雜,大部分時候需要進行轉換 |
橫表優點:
1:業務描述:橫表的好處是清晰可見,一目了然,數據描敘很清晰。每個字段就是一個KPI指標。
2:性能方面:橫表從數據庫映射到內存的速度比豎表要快很多。
3:代碼復雜:橫表不需要做行列轉換,代碼比較簡單
橫表缺點:
1:可擴展性:如果需求變更,例如需要增加一個指標,那么就必須修改表結構或重建表。對於需求不明確或變更頻繁的情況,橫表需要
大的改動,涉及改動的腳本也較多。
豎表優點:
1:可擴展性:對於豎表來說,不必修改表結構,只需增加一條記錄就可搞定。對於需求不明確或變更頻繁的情況,豎表基本不用改動,
涉及改動的腳本也較少。
豎表缺點:
1:業務描述:豎表的數據描敘很不清晰,舉例說明:學生成績表的豎表形式,成績這個字段,即可是數學成績、也可是語文成績,不像
橫表形式數學成績、語文成績各成一個字段描述KPI指標來得清晰明了。
2:性能方面:系統展現的報表大部分是橫表,這意味着豎表要進行轉列。這樣需要額外的性能開銷。尤其是當報表進行聚合計算時,性能
更糟糕。這是因為豎表從數據庫映射到內存比橫表要慢。
3:代碼復雜:需要做行列轉換,代碼量、復雜性都會增加很多。
實驗對比
橫表STUDENT_SCORE有語文成績、英語成績等7個KPI指標,三個學生的三條記錄。
SQL> SELECT * FROM STUDENT_SCORE; STUDENT_NO CHINESE_SCORE ENGLISH_SCORE MATH_SOCRE PHYSICAL_SCORE SPORTS_SCORE CHEMICAL_SCORE BIOLOGICAL_SCORE ----------- ------------- ------------- ---------- -------------- ------------ -------------- ---------------- 10001 87.4 63 92 86 75 85 89 10002 91 89 98 62 76 82 73 10006 74 63 57 42 76 59 67
對應於豎表,這三個學生的7個KPI指標需要21條記錄才能描述清楚。
SQL> SELECT * FROM STUDENT_SOCRES; STUDENT_NO SUBJECT_CODE SUBJECT_NAME SCORES ----------- ------------ ------------ ---------- 10001 CH 語文成績 87.4 10001 EG 英語成績 63 10001 MT 數學成績 92 10001 PH 物理成績 86 10001 SP 體育成績 75 10001 CE 化學成績 85 10001 BI 生物成績 89 10002 CH 語文成績 91 10002 EG 英語成績 89 10002 MT 數學成績 98 10002 PH 物理成績 62 10002 SP 體育成績 76 10002 CE 化學成績 82 10002 BI 生物成績 73 10006 CH 語文成績 74 10006 EG 英語成績 63 10006 MT 數學成績 57 10006 PH 物理成績 42 10006 SP 體育成績 76 10006 CE 化學成績 59 10006 BI 生物成績 67
所以我們從這個小實驗中可以看到,橫表轉成豎表,對應的記錄會翻倍增長,這對應於數據量大的表或寬表,都是一件不好的消息。很多時候,
數據量上去了,性能問題就出來了。它們之間的記錄關系如下所示:
豎表行數 = 橫表行數 * KPI指標個數。
一個數據量500萬,KPI指標個數為10的橫表,轉成豎表后的記錄數會飈增到5000萬。
我們拿表ODS.TO_BUSS_WNMS_BSCPMHR來做實驗,該表的表結構對應如下所示:
SQL> DESC ODS.TO_BUSS_WNMS_BSCPMHR Name Type Nullable Default Comments --------------------------- ---------- -------- ------- ----------------------- COLLECT_DT NUMBER(8) 采集日期 DATE_CD NUMBER(8) 日期編碼 HR_CD NUMBER(2) 時段編碼 CITY_ID NUMBER(10) 地市標識 SYSTEM_ID NUMBER(10) 網元編碼 TBF_CLEAN_CNT NUMBER Y TBF清空次數 UPTBF_TRY_CNT NUMBER Y 上行TBF請求數 UPTBF_SUCC_RAT NUMBER Y 上行TBF建立成功率 DOWNTBF_SUCC_CNT NUMBER Y 下行TBF成功建立次數 DOWNTBF_SUCC_RAT NUMBER Y 下行TBF建立成功率 DOWNTBF_TRY_CNT NUMBER Y 下行TBF建立嘗試次數 UPTBF_SUCC_CNT NUMBER Y 上行TBF成功建立次數 GPRSDOWNTBF_ABNM_CNT NUMBER Y GPRS下行TBF異常中斷次數 GPRSDOWNTBF_SUCC_CNT NUMBER Y GPRS下行TBF建立成功次數 GPRSDOWNTBF_DROP_RAT NUMBER Y GPRS下行TBF掉線率 DROP_CALL_TCH NUMBER Y TCH掉話總次數 TCH_CALL_SEIZ NUMBER Y 話音信道占用總次數(含切換) GSMTCH_DROP_RAT NUMBER Y TCH掉話率(GSM) TDTCH_DROP_PCT NUMBER Y TCH話務掉話比 TCH_ERL NUMBER Y TCH話務量 TCH_CNT NUMBER Y TCH信道數 TCH_GT_RAT NUMBER Y TCH接通率 ATT_TCH_OVRFL NUMBER Y 話音信道溢出總次數(含切換) TCH_CALL_REQ NUMBER Y 話音信道試呼總次數(含切換) TCH_CONG_RAT NUMBER Y TCH擁塞率 DROP_CALL_SDCCH NUMBER Y SDCCH掉話總次數 GSMSDCCH_DROP_RAT NUMBER Y SDCCH掉話率(GSM) SDCCH_ALLOT_SUCC_RAT NUMBER Y SDCCH分配成功率 SDCCH_GT_RAT NUMBER Y SDCCH接通率 ATT_SDCCH_OVRFL NUMBER Y SDCCH溢出總次數 SDCCH_CONG_RAT NUMBER Y SDCCH擁塞率 SDCCH_USE_CNT NUMBER Y SDCCH占用次數 SDCCH_TRY_CNT NUMBER Y SDCCH試呼次數 SDCCH_AV_HOLD_T NUMBER Y SDCCH信道平均占用時長 RLC_TRAFIC NUMBER Y RLC流量 EDGE_RLCTSTP_RAT_FZ NUMBER Y EGPRS RLC層單時隙吞吐率-分子 EDGE_RLCTSTP_RAT_FM NUMBER Y EGPRS RLC層單時隙吞吐率-分母 EGPRS_RLC_THRUPUT_RAT NUMBER Y EGPRS RLC層單時隙吞吐率 GPRS_RLCTSTP_RAT_FZ NUMBER Y GPRS RLC層單時隙吞吐率-分子 GPRS_RLCTSTP_RAT_FM NUMBER Y GPRS RLC層單時隙吞吐率-分母 GPRS_RLC_THRUPUT_RAT NUMBER Y GPRS RLC層單時隙吞吐率 EGPRS_RETRAN_RAT NUMBER Y EGPRS重傳率 RLCDOWN_REPLYDATA_EGPRS_CNT NUMBER Y RLC層下行鏈路無線數據塊重傳數(EGPRS) EGPRS_RLC_CNT NUMBER Y RLC層總塊數(EGPRS) GPRS_RETRAN_RAT NUMBER Y GPRS重傳率 RLCDOWN_REPLYDATA_GPRS_CNT NUMBER Y RLC層下行鏈路無線數據塊重傳數(GPRS) GPRS_RLC_CNT NUMBER Y RLC層總塊數(GPRS) LOW_CODE_RAT NUMBER Y 低編碼比例 MID_CODE_RAT NUMBER Y 中編碼比例 USE_PDCH_AVG_CNT NUMBER Y 占用的PDCH的平均數 PDCH_REUSE NUMBER Y PDCH復用度 PDCH_ALLOT_SUCC_RAT NUMBER Y PDCH信道分配成功率 PDCH_CNT NUMBER Y PDCH信道數量 GSL_MAX_CNT NUMBER Y PCU(GSL最大設備數) GSL_USERAT NUMBER Y GSL利用率 PDCH_ALLOT_CNT NUMBER Y PDCH信道分配次數 PDCH_ALLOT_SUCC_CNT NUMBER Y PDCH信道分配成功次數 HO_REQ_CNT NUMBER Y 切換請求總次數 HO_SUCC_CNT NUMBER Y 切換成功總次數 AVAIL_TCH_NBR NUMBER Y 可配置信道數 TCH_TRAFFIC_H NUMBER Y 半速率話務量 CH_CNT_PDCH NUMBER Y 總業務信道數 TCH_SEIZE_NHO NUMBER Y 話音信道占用總次數(不含切換) AVG_DISTR_PDCH_CNT NUMBER Y 平均分配PDCH數 UPIP_FLOW NUMBER Y 上行IP層流量 DOWNIP_FLOW NUMBER Y 下行IP層流量
建立這張橫表對應的豎表TO_BUSS_WNMS_BSCPM_H_TEST
CREATE TABLE TO_BUSS_WNMS_BSCPM_H_TEST ( COLLECT_DT NUMBER(8) , DATE_CD NUMBER(8) , HR_CD NUMBER(2) , CITY_ID NUMBER(10) , SYSTEM_ID NUMBER(10) , KPI_CODE VARCHAR2(32), KPI_NAM VARCHAR2(32), KPI_VALUE NUMBER , CONSTRAINT PK_TO_BUSS_WNMS_BSCPM_H_TEST PRIMARY KEY ("COLLECT_DT", "DATE_CD", "HR_CD", "CITY_ID", "SYSTEM_ID","KPI_CODE") ) PARTITION BY RANGE(COLLECT_DT) ( PARTITION "PART201111" VALUES LESS THAN (20111199) TABLESPACE TBS_KFT_DATA, PARTITION "PART201112" VALUES LESS THAN (20111299) TABLESPACE TBS_KFT_DATA, PARTITION "PART201201" VALUES LESS THAN (20120199) TABLESPACE TBS_KFT_DATA, PARTITION "PART201202" VALUES LESS THAN (20120299) TABLESPACE TBS_KFT_DATA, PARTITION "PART201203" VALUES LESS THAN (20120399) TABLESPACE TBS_KFT_DATA, PARTITION "PART201204" VALUES LESS THAN (20120499) TABLESPACE TBS_KFT_DATA, PARTITION "PART201205" VALUES LESS THAN (20120599) TABLESPACE TBS_KFT_DATA, PARTITION "PART201206" VALUES LESS THAN (20120699) TABLESPACE TBS_KFT_DATA, PARTITION "PART201207" VALUES LESS THAN (20120799) TABLESPACE TBS_KFT_DATA, PARTITION "PART201208" VALUES LESS THAN (20120899) TABLESPACE TBS_KFT_DATA, PARTITION "PART201209" VALUES LESS THAN (20120999) TABLESPACE TBS_KFT_DATA, PARTITION "PART201210" VALUES LESS THAN (20121099) TABLESPACE TBS_KFT_DATA, PARTITION "PART201211" VALUES LESS THAN (20121199) TABLESPACE TBS_KFT_DATA, PARTITION "PART201212" VALUES LESS THAN (20121299) TABLESPACE TBS_KFT_DATA, PARTITION "PART201301" VALUES LESS THAN (20130199) TABLESPACE TBS_KFT_DATA )
把ODS.TO_BUSS_WNMS_BSCPMHR的2012年12月1號以后的數據導入到TO_BUSS_WNMS_BSCPM_H_TEST,然后收集統計該表的相關信息。另外新建橫表
TO_BUSS_WNMS_BSCPMHR_S_TEST(表結構和ODS.TO_BUSS_WNMS_BSCPMHR一樣),把2012年12月1號以后的數據導入到TO_BUSS_WNMS_BSCPMHR_S_TEST
SQL> exec dbms_stats.gather_table_stats(ownname=>'DWKONGLINGBO',tabname=>'TO_BUSS_WNMS_BSCPMHR_S_TEST',partname=>'PART201212',granularity=>'PARTITION',estimate_percent=> dbms_stats.auto_sample_size,force=>true,degree=>8); PL/SQL procedure successfully completed SQL> exec dbms_stats.gather_table_stats(ownname=>'DWKONGLINGBO',tabname=>'TO_BUSS_WNMS_BSCPMHR_S_TEST',partname=>'PART201301',granularity=>'PARTITION',estimate_percent=> dbms_stats.auto_sample_size,force=>true,degree=>8); PL/SQL procedure successfully completed SQL> exec dbms_stats.gather_table_stats(ownname=>'DWKONGLINGBO',tabname=>'TO_BUSS_WNMS_BSCPM_H_TEST',partname=>'PART201212',granularity=>'PARTITION',estimate_percent=> dbms_stats.auto_sample_size,force=>true,degree=>8); PL/SQL procedure successfully completed SQL> exec dbms_stats.gather_table_stats(ownname=>'DWKONGLINGBO',tabname=>'TO_BUSS_WNMS_BSCPM_H_TEST',partname=>'PART201301',granularity=>'PARTITION',estimate_percent=> dbms_stats.auto_sample_size,force=>true,degree=>8); PL/SQL procedure successfully completed
查看表的相關信息:
SELECT TABLE_OWNER, TABLE_NAME, PARTITION_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED FROM DBA_TAB_PARTITIONS WHERE TABLE_NAME = 'TO_BUSS_WNMS_BSCPMHR_S_TEST' AND PARTITION_NAME IN ('PART201212', 'PART201301')
SELECT TABLE_OWNER, TABLE_NAME, PARTITION_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED FROM DBA_TAB_PARTITIONS WHERE TABLE_NAME = 'TO_BUSS_WNMS_BSCPM_H_TEST' AND PARTITION_NAME IN ('PART201212', 'PART201301')
對比上面統計信息,即可發現:
SQL> SELECT 43926464/721062 FROM DUAL; 43926464/721062 --------------- 60.919122072720 SQL> SELECT 26921828/443940 FROM DUAL; 26921828/443940 --------------- 60.642942740009
可見數據量翻了60~61倍,數據所占存儲空間增長了7倍,也就說數據冗余大量增加。可見在存儲方面,橫表要比豎表有優勢。如果系統大量使用豎表,
存儲浪費就比較嚴重了。
SQL> SELECT (102240-12753)/12753 FROM DUAL; (102240-12753)/12753 -------------------- 7.01693719124912 SQL> SELECT (63303 -7622)/7622 FROM DUAL; (63303-7622)/7622 ----------------- 7.30530044607715
查詢腳本對比
從下面的執行計划,以及實際執行結果可以看出,橫表比豎表的性能要優很多。我測試了好幾次。都是如此,而且腳本越復雜,執行效率差異越大。
SQL> SET AUTOTRACE TRACEONLY SQL> SET AUTOTRACE TRACEONLY SQL> SELECT A.COLLECT_DT AS COLLECT_DT, A.DATE_CD AS DATE_CD, A.HR_CD AS HR_CD, A.CITY_ID AS CITY_ID, A.SYSTEM_ID AS BSC_ID, B.MSC_ID AS MSC_ID, MAX(DECODE(A.KPI_CODE, 'HO_REQ_CNT', KPI_VALUE, 0)) AS HO_REQ, MAX(DECODE(A.KPI_CODE, 'HO_SUCC_CNT', KPI_VALUE, 0)) AS S_HO FROM dwkonglingbo.TO_BUSS_WNMS_BSCPM_H_TEST A, REF.TR_WGG_BSC_INFO B WHERE A.SYSTEM_ID = B.BSC_ID AND A.COLLECT_DT = 20121218 AND (KPI_CODE = 'HO_REQ_CNT' OR KPI_CODE = 'HO_SUCC_CNT') GROUP BY A.COLLECT_DT, A.DATE_CD, A.HR_CD, A.CITY_ID, A.SYSTEM_ID, B.MSC_ID; 22728 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 2342193993 --------------------------------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | Pstart| Pstop | --------------------------------------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 46458 | 2722K| | 239 (3)| 00:00:06 | | | | 1 | HASH GROUP BY | | 46458 | 2722K| 7328K| 239 (3)| 00:00:06 | | | |* 2 | HASH JOIN | | 46458 | 2722K| | 9 (45)| 00:00:01 | | | | 3 | TABLE ACCESS FULL | TR_WGG_BSC_INFO | 1963 | 21593 | | 2 (0)| 00:00:01 | | | | 4 | TABLE ACCESS BY GLOBAL INDEX ROWID| TO_BUSS_WNMS_BSCPM_H_TEST | 46458 | 2223K| | 7 (58)| 00:00:01 | 14 | 14 | |* 5 | INDEX RANGE SCAN | PK_TO_BUSS_WNMS_BSCPM_H_TEST | 46458 | | | 6 (67)| 00:00:01 | | | --------------------------------------------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("A"."SYSTEM_ID"="B"."BSC_ID") 5 - access("A"."COLLECT_DT"=20121218) filter("KPI_CODE"='HO_REQ_CNT' OR "KPI_CODE"='HO_SUCC_CNT') Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 49816 consistent gets 89 physical reads 6408 redo size 1177246 bytes sent via SQL*Net to client 17157 bytes received via SQL*Net from client 1517 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 22728 rows processed SQL> SELECT A.COLLECT_DT AS COLLECT_DT, 2 A.DATE_CD AS DATE_CD, 3 A.HR_CD AS HR_CD, 4 A.CITY_ID AS CITY_ID, 5 A.SYSTEM_ID AS BSC_ID, 6 B.MSC_ID AS MSC_ID, 7 A.HO_REQ_CNT AS HO_REQ, 8 A.HO_SUCC_CNT AS S_HO 9 FROM dwkonglingbo.TO_BUSS_WNMS_BSCPMHR_S_TEST A, REF.TR_WGG_BSC_INFO B 10 WHERE A.SYSTEM_ID = B.BSC_ID 11 AND A.COLLECT_DT = 20121218; 22728 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 2525980157 -------------------------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | -------------------------------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 23260 | 1044K| 31 (4)| 00:00:01 | | | |* 1 | HASH JOIN | | 23260 | 1044K| 31 (4)| 00:00:01 | | | | 2 | TABLE ACCESS FULL | TR_WGG_BSC_INFO | 1963 | 21593 | 2 (0)| 00:00:01 | | | | 3 | TABLE ACCESS BY GLOBAL INDEX ROWID| TO_BUSS_WNMS_BSCPMHR_S_TEST | 23260 | 795K| 28 (0)| 00:00:01 | 2 | 2 | |* 4 | INDEX RANGE SCAN | PK_TO_BUSS_WNMS_BSCPMHR_S_TEST | 23260 | | 2 (0)| 00:00:01 | | | -------------------------------------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("A"."SYSTEM_ID"="B"."BSC_ID") 4 - access("A"."COLLECT_DT"=20121218) Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 3449 consistent gets 434 physical reads 0 redo size 1175708 bytes sent via SQL*Net to client 17157 bytes received via SQL*Net from client 1517 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 22728 rows processed SQL>
結論總結:(上述測試列子比較少,也沒有排除其他因素的影響,但是足以說明實質問題)
豎表只適合數據量少,需求變更比較頻繁或配置比較靈活的報表,例如概覽視圖等。不適合數據量大的表。也不適合在數據倉庫中大量存在。