概念介绍
横表概念
横表就是普通的建表方式,每一个字段代表一个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
----------- ------------ ------------ ----------
CH 语文成绩 87.4
EG 英语成绩 63
MT 数学成绩 92
PH 物理成绩 86
SP 体育成绩 75
CE 化学成绩 85
BI 生物成绩 89
CH 语文成绩 91
EG 英语成绩 89
MT 数学成绩 98
PH 物理成绩 62
SP 体育成绩 76
CE 化学成绩 82
BI 生物成绩 73
CH 语文成绩 74
EG 英语成绩 63
MT 数学成绩 57
PH 物理成绩 42
SP 体育成绩 76
CE 化学成绩 59
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;
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):
---------------------------------------------------
- access("A"."SYSTEM_ID"="B"."BSC_ID")
- access("A"."COLLECT_DT"=20121218)
filter("KPI_CODE"='HO_REQ_CNT' OR "KPI_CODE"='HO_SUCC_CNT')
Statistics
----------------------------------------------------------
recursive calls
db block gets
consistent gets
physical reads
redo size
bytes sent via SQL*Net to client
bytes received via SQL*Net from client
SQL*Net roundtrips to/from client
sorts (memory)
sorts (disk)
rows processed
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,
A.HO_REQ_CNT AS HO_REQ,
A.HO_SUCC_CNT AS S_HO
FROM dwkonglingbo.TO_BUSS_WNMS_BSCPMHR_S_TEST A, REF.TR_WGG_BSC_INFO B
WHERE A.SYSTEM_ID = B.BSC_ID
AND A.COLLECT_DT = 20121218;
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):
---------------------------------------------------
- access("A"."SYSTEM_ID"="B"."BSC_ID")
- access("A"."COLLECT_DT"=20121218)
Statistics
----------------------------------------------------------
recursive calls
db block gets
consistent gets
physical reads
redo size
bytes sent via SQL*Net to client
bytes received via SQL*Net from client
SQL*Net roundtrips to/from client
sorts (memory)
sorts (disk)
rows processed
SQL>
结论总结:(上述测试列子比较少,也没有排除其他因素的影响,但是足以说明实质问题)
竖表只适合数据量少,需求变更比较频繁或配置比较灵活的报表,例如概览视图等。不适合数据量大的表。也不适合在数据仓库中大量存在。