數倉教程 +數倉開發規范


數倉的分層總結

ODS:(原始數據層): 原始,對采集的數據不做處理!
DWD: (明細數據層): 對原始數據層的數據,展開明細,進行ETL過濾!
DWS: (數據服務層): 基於ADS需要統計的主題,創建寬表
ADS: (應用數據層): 基於DWS的寬表,計算出結果

范式

范式:數據庫在設計建模時需要遵守的規范和樣式!

好處和目的:
①減少數據冗余
②保證數據的一致性

第一范式: 列具有原子性,不能再拆
第二范式: 所有非主鍵列必須完全依賴於主鍵,不能存在部分函數依賴
第三范式: 不能存在傳遞函數依賴

建模

關系(ER)建模: 抽象程度高!側重於數據的治理!強調數據的整合(不能冗余)和數據的一致性!
關系型數據庫中,常用ER建模!

維度建模: 維度建模一般是面向某個業務(主題),將這個業務所需要的有相同特征的數據
放入到一個維度中!特點是會造成數據的冗余(寬表),好處是面向業務,在具體業務的計算時,
效率高!

大數據領域,一般都使用維度建模! hive中 表與表之間的join效率低!
hbase中不支持表的join!
采用維度建模,創建寬表!把某個業務所需的所有數據全部集中在表中!

維度建模的常見模型
星型模型: 維度表直接關聯在事實表上!
雪花模型: 維度表間接關聯在事實表上!
星座模型: 本質上是一種特殊的星型模型!在一個業務場景中,存在多個事實表!
多個事實表共享某個維度表!

 with語法 的使用

with t1 as
(select 
    id user_id,gender user_gender,
    ceil(months_between('2020-02-16',birthday)/12) user_age,
    user_level
from  dwd_user_info
where dt='2020-02-16'),
t2 as 
(select 
    id sku_id,price order_price,sku_name,tm_id sku_tm_id,
    category3_id sku_category3_id,
    category2_id sku_category2_id,
    category1_id sku_category1_id,
    category3_name sku_category3_name, 
    category2_name sku_category2_name, 
    category1_name sku_category1_name,
    spu_id spu_id
from  dwd_sku_info
where  dt='2020-02-16'),
t3 as 
(select 
    orderdatail.sku_num,orderdatail.sku_id,orderdatail.user_id
from ods_order_detail orderdatail join ods_payment_info payment
on orderdatail.order_id = payment.order_id
),
t4 as 
(select 
    orderdatail.sku_id,orderdatail.user_id,
    count(*) order_count,
    sum(orderdatail.order_price*orderdatail.sku_num) order_amount
from ods_order_detail orderdatail join ods_payment_info payment
on orderdatail.order_id = payment.order_id
group by orderdatail.sku_id,orderdatail.user_id)
insert overwrite TABLE dws_sale_detail_daycount PARTITION(dt='2020-02-16')
select 
    t1.user_id,t2.sku_id,t1.user_gender,t1.user_age,t1.user_level,
    t2.order_price,t2.sku_name,t2.sku_tm_id,t2.sku_category3_id,
    t2.sku_category2_id,t2.sku_category1_id,
    t2.sku_category3_name,t2.sku_category2_name,t2.sku_category1_name,
    t2.spu_id,t3.sku_num,t4.order_count,t4.order_amount
from  t4 join t3 
on t4.sku_id=t3.sku_id and t4.user_id=t3.user_id
join t1  on t1.user_id=t3.user_id
join t2 on t3.sku_id=t2.sku_id

if語句得使用

select
    sku_tm_id, sku_category1_id,sku_category1_name,
    sum(if(order_count_per_mn>=1,1,0)) buycount,
    sum(if(order_count_per_mn>=2,1,0)) buy_twice_last,
    cast(sum(if(order_count_per_mn>=2,1,0))/sum(if(order_count_per_mn>=1,1,0))*100 as decimal(10,2)) buy_twice_last_ratio,
    sum(if(order_count_per_mn>=3,1,0)) buy_3times_last,
    cast(sum(if(order_count_per_mn>=3,1,0)) / sum(if(order_count_per_mn>=1,1,0)) * 100 as decimal(10,2)) buy_3times_last_ratio,
    date_format('2020-02-16','yyyy-MM') stat_mn,
    '2020-02-16'
from 
(select 
    user_id,sku_tm_id,count(order_count) order_count_per_mn,sku_category1_id,sku_category1_name
from  dws_sale_detail_daycount
where date_format(dt,'yyyy-MM')=date_format('2020-02-16','yyyy-MM')
group by sku_tm_id,user_id,sku_category1_id,sku_category1_name ) tmp
group by sku_tm_id,sku_category1_id,sku_category1_name

 

數倉各層級腳本 

#!/bin/bash
#向ods的兩個表中導入每天的數據,為數據創建LZO索引
#接受要導入數據的日期參數,-n可以判斷后面的參數是否為賦值,如果賦值,返回true,否則返回false
#為判斷的變量名加雙引號
if [ -n "$1" ]
then
     do_date=$1
else
    do_date=$(date -d yesterday +%F)
fi

echo ===日志日期為$do_date===

APP=gmall
sql="
load data inpath '/origin_data/gmall/log/topic_start/$do_date' into table $APP.ods_start_log partition(dt='$do_date');

load data inpath '/origin_data/gmall/log/topic_event/$do_date' into table $APP.ods_event_log partition(dt='$do_date');
"
hive  -e "$sql"

hadoop jar /opt/module/hadoop-2.7.2/share/hadoop/common/hadoop-lzo-0.4.20.jar com.hadoop.compression.lzo.DistributedLzoIndexer /warehouse/gmall/ods/ods_start_log/dt=$do_date

hadoop jar /opt/module/hadoop-2.7.2/share/hadoop/common/hadoop-lzo-0.4.20.jar com.hadoop.compression.lzo.DistributedLzoIndexer /warehouse/gmall/ods/ods_event_log/dt=$do_date
ods層
#!/bin/bash
if [ -n "$1" ]
then
     do_date=$1
else
    do_date=$(date -d yesterday +%F)
fi

echo ===日志日期為$do_date===


sql="

"
hive  -e "$sql"
每層腳本模板
#!/bin/bash
if [ -n "$1" ]
then
     do_date=$1
else
    do_date=$(date -d yesterday +%F)
fi

echo ===日志日期為$do_date===


sql="

insert overwrite table gmall.dwd_start_log
PARTITION (dt='$do_date')
select 
    get_json_object(line,'$.mid') mid_id,
    get_json_object(line,'$.uid') user_id,
    get_json_object(line,'$.vc') version_code,
    get_json_object(line,'$.vn') version_name,
    get_json_object(line,'$.l') lang,
    get_json_object(line,'$.sr') source,
    get_json_object(line,'$.os') os,
    get_json_object(line,'$.ar') area,
    get_json_object(line,'$.md') model,
    get_json_object(line,'$.ba') brand,
    get_json_object(line,'$.sv') sdk_version,
    get_json_object(line,'$.g') gmail,
    get_json_object(line,'$.hw') height_width,
    get_json_object(line,'$.t') app_time,
    get_json_object(line,'$.nw') network,
    get_json_object(line,'$.ln') lng,
    get_json_object(line,'$.la') lat,
    get_json_object(line,'$.entry') entry,
    get_json_object(line,'$.open_ad_type') open_ad_type,
    get_json_object(line,'$.action') action,
    get_json_object(line,'$.loading_time') loading_time,
    get_json_object(line,'$.detail') detail,
    get_json_object(line,'$.extend1') extend1
from gmall.ods_start_log 
where dt='$do_date';


"
hive  -e "$sql"
dwd層

 

get_json_object 的使用

get_json_object的介紹

get_json_object(json_txt, path) - Extract a json object from path 從json對象中抽取指定的內容
Extract json object from a json string based on json path specified,
and return json string of the extracted json object.
It will return null if the input json string is invalid.

需要指定要抽取的內容的path路徑!
如果函數傳入的數據不是JSON,此時會返回Null!

參數介紹如下:

A limited version of JSONPath supported:
$ : Root object 代表整個JSON對象
. : Child operator 代表獲取JSON對象中子元素(屬性)的操作符
[] : Subscript operator for array 獲取JSONArray中的某個元素
案列:
{"name":"jack","age":18,"parents":[{"name":"oldjack","age":48},{"name":"jackmom","age":49}]}

獲取18
select get_json_object('{"name":"jack","age":18,"parents":
[{"name":"oldjack","age":48},{"name":"jackmom","age":48}]}','$.age')

獲取49
select get_json_object('{"name":"jack","age":18,"parents":
[{"name":"oldjack","age":48},{"name":"jackmom","age":49}]}','$.parents[1].age')

綜合案列

select line
,get_json_object(line,'$.action') as action
,get_json_object(line,'$.ba') as ba
,get_json_object(line,'$.g') as gmaill
from    gmall.ods_start_log
where dt='2021-03-15' limit 10 ; 
執行結果:

 

 

 lateral view 的使用

描述

lateral view用於和split, explode等UDTF一起使用,它能夠將一行數據拆成多行數據,在此基礎上可以對拆分后的數據進行聚合。lateral view首先為原始表的每行調用UDTF,UTDF會把一行拆分成一或者多行,lateral view再把結果組合,產生一個支持別名表的虛擬表。

例子

假設我們有一張表pageAds,它有兩列數據,第一列是pageid string,第二列是adid_list,即用逗號分隔的廣告ID集合:

create table pageAds 
as select   'front_page' as pageid,array(1,2,3) as adid_list  
union all 
select   'contact_page' as pageid,array(3,4,5) as adid_list
-----表里的數據如下:兩行數據
pageid      adid_list
front_page    [1,2,3]
contact_page    [3,4,5]

要統計所有廣告ID在所有頁面中出現的次數。

首先分拆廣告ID

select pageid,adid 
from pageAds
lateral view explode (adid_list) ad_table as adid
----執行結果如下:
pageid              adid 
front_page            1
front_page           2
front_page            3
contact_page           3
contact_page           4
contact_page              5

接下來就是一個聚合的統計

select adid ,count(1) as cn 
from pageAds
lateral view explode (adid_list) ad_table as adid
group by adid
----執行結果如下:
adid  cn 
1    1
2    1
3    2
4    1
5    1

concat_ws和collect_set結合使用把列表用指定符號拼接

---創建表
create table temp_set_test
(  INT,
user_id  int,
version_code string,
version_name string
)
----插入測試數據
insert into temp_set_test values (11,11000,'aa','aa_alex'),(11,12000,'aa','aa_alex'),(11,13000,'cc','cc_alex')
insert into temp_set_test values (22,22000,'bb','bb_alex'),(22,23000,'cc','cc_alex'),(22,24000,'dd','dd_alex')
-----查看數據
 select *     
from temp_set_test
---執行結果如下:
mid_id  user_id   version_code   version_name
11          11000           aa             aa_alex
11          12000           aa             aa_alex
11          13000           cc             cc_alex
22          22000           bb             bb_alex
22          23000           cc             cc_alex
22          24000           dd             dd_alex


----把每個mid_id的結果集 轉成一列 列表 
select mid_id
,collect_set(version_code) as result
from temp_set_test
group by mid_id
---執行結果如下:
mid_id   result
11          ["aa","cc"]
22          ["bb","cc","dd"]

----把每一行結果集 用符號(_)進行拼接  collect_list和collect_set的區別就是是否進行去重
select mid_id
,concat_ws('_',collect_list(version_code)  ) as result
from temp_set_test
group by mid_id
---執行結果如下:
mid_id  result
11         aa_aa_cc
22         bb_cc_dd
set hive.exec.dynamic.partition.mode=nonstrict;
insert overwrite TABLE gmall.dws_uv_detail_wk PARTITION(wk_dt)
SELECT 
mid_id, 
concat_ws('|',collect_set(user_id)) user_id,
concat_ws('|',collect_set(version_code)) version_code,
concat_ws('|',collect_set(version_name)) version_name, 
concat_ws('|',collect_set(lang)) lang, 
concat_ws('|',collect_set(source)) source,
concat_ws('|',collect_set(os)) os, 
concat_ws('|',collect_set(area)) area, 
concat_ws('|',collect_set(model)) model,
concat_ws('|',collect_set(brand)) brand,
concat_ws('|',collect_set(sdk_version)) sdk_version,
concat_ws('|',collect_set(gmail)) gmail,
concat_ws('|',collect_set(height_width)) height_width,
concat_ws('|',collect_set(app_time)) app_time,
concat_ws('|',collect_set(network)) network,
concat_ws('|',collect_set(lng)) lng, 
concat_ws('|',collect_set(lat)) lat,
date_sub(next_day('2020-02-14','mo'),7) monday_date,
date_sub(next_day('2020-02-14','mo'),1) sunday_date,
concat(date_sub(next_day('2020-02-14','mo'),7),'-',date_sub(next_day('2020-02-14','mo'),1))
FROM gmall.dws_uv_detail_day
where dt BETWEEN date_sub(next_day('2020-02-14','mo'),7)
and date_sub(next_day('2020-02-14','mo'),1) 
group by mid_id;
案列

 

 連續三天登陸

邏輯就是根據日期自增一天,給用戶ID 也自增一列,日期和自增列相減的結果值一樣的個數 表示連續登陸的天數

----准備測試數據
create table test_lianxu
(mid_id INT,
create_time string 

)
insert into test_lianxu values (1,'2021-03-01'),(1,'2021-03-03'),(1,'2021-03-04'),(1,'2021-03-05'),(1,'2021-03-08') 

insert into test_lianxu values (2,'2021-03-01'),(2,'2021-03-05'),(2,'2021-03-06'),(2,'2021-03-08'),(2,'2021-03-10') 
insert into test_lianxu values (4,'2021-03-01'),(4,'2021-03-05'),(4,'2021-03-06'),(4,'2021-03-09'),(4,'2021-03-18') 


insert into test_lianxu values (0,'2021-03-01'),(0,'2021-03-02'),(0,'2021-03-03'),(0,'2021-03-04'),(0,'2021-03-05'),(0,'2021-03-06')
測試數據准備
----子查詢結果集大於等於3 表示3天連續登陸
select 
temp.mid_id,temp.date_diff
,count(1)
from (
------計算出明細
select 
mid_id
,create_time
,row_number() over(partition by mid_id order by create_time asc) as rn 
,date_sub(create_time,row_number() over(partition by mid_id order by create_time asc)) as date_diff  ---計算出日期和自增列的差值
from test_lianxu
) temp 
group by temp.mid_id,temp.date_diff
having count(1) >=3 
---查詢結果如下:
0    2021-02-28    6
1    2021-03-01    3
3    2021-03-03    3

數倉開發紅線

1. hera遷移必須完全做到任務名和數據插入表名完全一致
2. 線上數倉(不含Temp、test、APP)的動態分區表,禁用 insert overwrite table ${job_name} partition(dt),改用 insert into ${job_name} partition(dt)
3. temp,test庫里,只放臨時表和測試用的表,並可隨時刪除的
4. 禁止對帶分區的直接表加列(萬一要加,要先把數據移走備份,重建表再把數據插回)
5. hera遷移完成之后,收回直接登錄主機操作權限(非數據平台管理員一律不能登錄主機操作)
6. 數倉直接修改數據規范:禁止直接 INSERT 或 TRUNCATE,只要不是通過任務對數據的修改,都要走SQL審批
7. 數倉重跑任務: 本人只能重跑自己的任務,依賴本任務的下游的任務只能由數據平台管理員或具有平台管理員權限的人重跑:
a. 帶分區的數據表重跑數據
A. 先手工備份原數據
B. 刪除需重跑數據分區
C. 修復分區
D. 重跑任務
8.數倉開發,驗數,試跑只能在test庫,驗數完成后,業務方發上線郵件(郵件發給業務方領導,大數據領導,數倉開發工程師)才能上線(上線后再修改,需同樣發郵件,並且一周只響應一次修改)

如果不按規范執行,相關責任人的績效將會降一個級別

數倉開發規范


1. 便於數據的統一管理和使用,達到見表識其義,且易於維護,制定此操作規范,此規范針對本部門開發人員,望共同遵守。

2. 分層規范
統一拉通層:
2.1 把DW層的數據做統一的清洗處理。去重、去噪、字典翻譯、空值轉化,日期格式化等操作。

2.1.1 DWD(明細層):
和ODS粒度一致的明細數據,對數據進行去重,臟數據過濾和砍字段處理,空處理,保證數據質量,簡單邏輯通過視圖實現,並解決數據的完整 度問題。

2.1.2 DWS(服務層):
輕度匯總數據及集市大寬表(按主題)存放數據。

2.2 DIM:( 維表層):
通過ods層獲取得到。

2.3 APP:(應用層):
存放應用類表數據,如標簽,各業務部門報表,第三方應用數據,按應用主題存放一般是業務部門,如銷售、風控、運營、財務等。

3. 表規范
3.1 命名
3.1.1 維表 命名形式:dim_描述
3.1.2 事實表 命名形式:fact_描述_[AB]
3.1.3 臨時表 命名形式:tmp_ 正式表名_ [C自定義序號]
3.1.4 橋接表 命名形式:map_主題_描述_[AB]
3.1.5 寬表 命名形式:dws_主題_描述_[AB]
3.1.6 備份表 命名形式:正式表名_bak_yyyymmdd

3.2 表命名解釋:
3.2.1 表名使用英文小寫字母,單詞之間用下划線分開,長度不超過40個字符,命名一般控制在小於等於6級。
3.2.2 其中ABC第一位"A"時間粒度:使用"c"代表當前數據,"h"代表小時數據,"d"代表天數據,"w"代表周數據,"m"代表月數據,"q"代表季度數據, "y" 代表年數據。
3.2.3 其中ABC的第二位"B"表示對象屬性,用"t"表示表,用"v"表示視圖。
3.2.4 其中ABC的第三位"C"自定義序號用於標識多個臨時表的跑數順序。
3.2.5 目前主題縮寫為:dev(設備)、user(用戶)、log(日志)、book(記賬)、bill(賬單)、loan(貸款)、fin(理財)、card(卡片)、mkt(營銷)。
3.2.6 橋接表主要用於一些多值維度,比如用戶和設備(存在多對多關聯)。

3.3 注釋
注釋要結合表的英文名,要求注釋簡潔明了,體現出表的業務出處、主題和用途。

3.4 表分區
在數倉中建立分區表統一用靜態分區,一般建立分區表時,將ymd作為一級分區,在dws、dwd層可根據具體業務邏輯,確定使用一級分區,或多級分區。

3.5 存儲格式
3.5.1 所謂的存儲格式就是在Hive建表的時候指定的將表中的數據按照什么樣子的存儲方式,如果指定了方式,那么在向表中插入數據的時候,將會使用該方式 3.5.2 向HDFS中添加相應的數據類型。在數倉中建表默認用的都是PARQUET存儲格式,相關語句如下所示:
stored as parquet TBLPROPERTIES ('parquet.compression'='SNAPPY')

3.6 字符集
Hadoop和hive 都是用utf-8編碼的,在建表時可能涉及到中文亂碼問題,所以導入的文件的字符編碼統一為utf-8格式。

3.7 約定
3.7.1 類型
1) 所有的表都應該使用內部表
和ods場景不同,數倉數據來源於ods,統一使用內部表,location默認位置(無需額外指定)
2) 分區表必須包含yyyy-MM-dd字段
yyyy-MM-dd在維表層便於識別數據更新日期,服務層便於查詢跑批數據。

3) 使用動態分區來插數據需要有相關設置
set hive.exec.dynamic.partition=true;
set hive.exec.dynamic.partition.mode=nonstrict;

在進行寬表數據初始化或者其它場景時,可能會涉及到使用動態分區的情況,此時應該對hive進行一些參數的設置。

3.8 會話級臨時表
3.8.1 在當前會話期間存在,會話結束后自動銷毀。
create TEMPORARY table xyy_temp.tablename

3.8.2 會話級的臨時表和temp庫下的臨時表使用場景不一樣,temp庫下的臨時表主要用來放一些需要加工的中間數據,方便跑數時出現問題好回溯,而會話級的 3.8.3 臨時表一般存放一些簡單但數據量大的中間結果,且一般不會被查詢使用,用來節省存儲空間。

3.9 空值
理論上在數倉落地的表不應該出現null未知類型,對於可能出現null的字段,如果為字符型統一為空字符串,如果是數值則給0。

4. 字段規范
4.1 命名
4.1.1 使用英文小寫字母,單詞之間用下划線分開,長度不超過30個字符,命名一般控制在小於等於4級;
4.1.2 和源數據bdl層表字段名一致,如為新增字段,盡量言簡意賅;
4.1.3 英文名盡量專業,符合業界要求,不得使用漢語拼音;
4.1.4 盡量避免使用關鍵字。如無法避免,使用”`”轉義;
4.1.5 指標字段能使用縮寫的盡量使用統一的縮寫,如申請金額統計apply_amt_sum,放款筆數loan_cnt,平均合同金額avg_contract_amt等,日后會建立一個詞根庫,用來規范一些縮寫名稱。

4.2 注釋
4.2.1 注釋本着簡潔、詳實、完整的原則,對於有業務含義的字段,在注釋中需要枚舉並解釋其業務含義,如odl_loan_apidata_order_info.order_status 訂 4.2.2 單狀態:3審核中,4申請被退回,41用戶取消審核,5審核不通過,6審核通過;

4.3 類型
4.3.1 目前暫時不會涉及到map、array、struct等復雜的數據類型,日期時間等格式統一用string類型,字符串也是用string,數值的話,會根據字段定義來確定,對於有小數點要求的,比如某些金額、利率,需要用到decimal類型,無小數點要求的用浮點類型double和整數類型(int,bigint)。

5.代碼規范
5.1 sql編碼
5.1.1 關鍵字右對齊,代碼注釋詳盡,查詢字段時每行不超過三個字段,縮進時空四格等相關書寫規范。
5.1.2 服務層依賴於ods層,應用層依賴於數倉層,原則上,不允許跨層查詢。
5.1.3 相對獨立的邏輯塊間加空行。
5.1.4 如果SQL語句連接多表時,應使用表的別名來引用列。
5.1.5 WHERE條件中參數與參數值使用的類型應當匹配,避免進行隱式類型轉化。
5.1.6 在SELECT語句中只獲取實際需要的字段。

5.2 shell腳本: 不建議使用shell腳本
5.2.1 調度腳本主要是通過跑shell腳本,shell腳本的注意點:
5.2.2 命名與所跑的目標表名相同,注釋要完善,后綴以.sh結尾。
5.2.3 腳本頭需要加上分割線、作者、日期、數據更新策略、目的、描述等信息。
5.2.4 腳本在末尾要給出腳本運行狀態,並且需要同步腳本運行狀態到門戶。
5.2.5 腳本里環境、配置等公用信息必須通過配置文件parameter_config。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM