Shell語言操控Mysql實戰——時間歷史拉鍊表的實現
介 紹
Shell語言在Linux上有自己獨到的優勢,特別是在Job排程層面,為了排程專案的規範,往往我們都喜歡把配置寫在資料庫內或者xml檔案上,這裡就舉列實戰下Shell語言和MySQL資料庫的互動,以實現經典的時間歷史拉鍊表(資料倉庫也把這種邏輯叫作緩慢漸變維)為例。
專案背景
很多情況下,新的資料不是固定時間如T+1生成的,可能是一週的某一天,或者是一個月的某一天,但是時間不固定,這個時候資料多了,就是工程師自己也無法知道某一時刻該用那一份資料,這個時候歷史拉鍊表就是一種解決辦法;
如圖1,鬼谷資產公司,該公司有很多資產,但是有一些可能沒有政府備案,有一些壞了,有一些特殊原因不讓賣,於是不定期銷售部會產生一個可售資源的資料檔案,把符合銷售規範的資料標出來,銷售人員根據這份資料檔案內的資料對外銷售,如20200102除了資料檔案1,則沒有新的資料檔案出來前,銷售一直按這份資產資料對外銷售,直到20200115日後,新的資料檔案2出來了,則改用資料檔案2,以此類推,那麼回顧歷史資料分析,任意哪天event_day,該用那份資料呢?請看時間歷史拉鍊表的實現。
CREATE TABLE `cfg_sale_his` ( `create_date` varchar(8) COLLATE utf8mb4_unicode_ci NOT NULL,`end_date` varchar(8) COLLATE utf8mb4_unicode_ci NOT NULL,`my_partition` varchar(8) COLLATE utf8mb4_unicode_ci DEFAULT NULL,`event_datetime` datetime DEFAULT NULL ) ENGINE=InnoDB AUTO_INCREMENT=67 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci |
cfg_sale_his的資料如下,最後一條記錄的最近的一個數據檔案,因為不知道什麼時候結束,所以end_date=‘99991231’,當新的資料檔案來了後,將原來end_date='99991231’的記錄修改為end_date=今天,並且新增一條記錄: create_date=今天,end_date=‘99991231’,my_partition=今天,event_datetime=now()即可。
+-------------+----------+-- --------+---------------------+ | create_date | end_date | my_partition | event_datetime | +-------------+----------+-----------+---------------------+ | 20200102 | 20200115 | 20200102 | 2020-01-15 18:33:15 | | 20200115 | 20200120 | 20200115 | 2020-01-20 21:36:47 | | 20200120 | 20200220 | 20200120 | 2020-02-20 22:00:57 | | 20200220 | 99991231 | 20200220 | 2020-02-20 22:15:46 | +-------------+----------+-----------+---------------------+
Shell互動實現程式碼
【題外篇】前段時間讀了篇部落格文章,掃了個知識盲區,可以分享下,shell的 #! 符號,專業名稱叫Shebang或者Shabang符號,Shebang都沒有正式的中文名稱。Linux中國翻譯組的GOLinux將其翻譯為“釋伴”,即“解釋伴隨行”的簡稱,同時又是Shebang的音譯。必須頂行寫,在直接呼叫指令碼時,系統的程式載入器會分析 Shebang 後的內容,將這些內容作為直譯器指令,並呼叫該指令,將載有 Shebang 的檔案路徑作為該直譯器的引數來執行指令碼,如我們熟悉的Shell指令碼開頭頂行的#!/bin/sh,在執行時會實際呼叫 /bin/sh 程式(通常是 Bourne shell 或相容的 shell,例如 bash、dash 等)來執行,同時Shebang 的內容會被這些指令碼直譯器自動忽略。
Shell實現cfg_sale_his表的邏輯具體如下,把這個指令碼命名為cfs_sale_his.sh吧:
#! /bin/bash
#User:Liuxw
#cfs_sale_his.sh
#設定三個引數,如果引數不是3個直接退出
if [[ $# -ne 3 ]];then
echo "parameters are incorrect"
exit 5
fi
#引數1 mysql密碼 引數2 重試連線mysql次數,有可能某次連線網路不好中斷了 引數3傳入 enddate的時間
PASSWORD=$1
RETRY_TIMES=$2
EVENT_DAY=$3
echo "PASSWORD: $PASSWORD"
echo "RETRY_TIMES: $RETRY_TIMES"
echo "EVENT_DAY: $EVENT_DAY"
#表有4列值,但是最後一列的event_time日期和時分秒的中間用了空格分隔,在linux的awk語法也會分列,所以是5列
SELECT_RET_FIELDS=5
#設定最新一條記錄的值為99991231
ENDLESS_DATE="99991231"
#設定event_time的值
CURRENT_DATE_WITH_MINUS_TIME=`date "+%Y-%m-%d %H:%M:%S"`
echo "SELECT_RET_FIELDS: $SELECT_RET_FIELDS"
echo "ENDLESS_DATE: $ENDLESS_DATE"
echo "CURRENT_DATE_WITH_MINUS_TIME: $CURRENT_DATE_WITH_MINUS_TIME"
#配置你的mysql連線ip 10.198.101.30 使用者 my_user 密碼為輸入的引數${PASSWORD},埠13300
#shell互動mysql的模板:mysql -h${db_ip} -u${db_user} -p${db_pawd} -P${db_port} -D${db_name} -s -e "${sql}"
MYSQL="mysql -h10.198.101.30 -umy_user -p${PASSWORD} -P13300 -A -N --default-character-set=utf8 dw_config"
#寫好你的sql語句
INSERT_SQL="insert into cfg_sale_his (create_date,end_date,my_partition,event_datetime )
values ( '$EVENT_DAY','$ENDLESS_DATE','$EVENT_DAY','$CURRENT_DATE_WITH_MINUS_TIME')"
UPDATE_SQL="update cfg_sale_his set end_date = '$EVENT_DAY' where end_date = '${ENDLESS_DATE}'"
SELECT_SQL="select * from cfg_sale_his where end_date = '${ENDLESS_DATE}' "
echo "insert sql: $INSERT_SQL"
echo "update sql: $UPDATE_SQL"
echo "select sql: $SELECT_SQL"
#配置執行sql的函式
function execute_sql() {
sql="$1"
select="select"
echo "ready to execute sql: $sql"
if [[ $sql =~ $select ]];then #$sql =~ $select的=~是正則匹配,看語句是否包含select
result=`$MYSQL -e "$sql"` #select語句有返回結果
echo "execute result: "
echo "$result"
ret=`echo "$result" | awk '{print NF}'`
#檢視select的結果是不是4列,因為event_time前面解釋了特殊,所以等於5,不然報錯
if [[ $ret -eq $SELECT_RET_FIELDS ]];then
echo "record with end_date = ${ENDLESS_DATE_WITH_MINUS} is : $result"
return 0
fi
else #不帶select的是insert,update語句
$MYSQL -e "$sql"
result=$?
echo "execute result: $result"
if [[ $result -ne 0 ]];then
echo "execute insert or update sql faild,sql: $sql"
ret=3
else
echo "execute insert or update sql success,sql: $sql"
ret=0
fi
fi
return $ret
}
#構建重試次數的函式,防止就試一次,這次連線還網路異常了
function retry_execute() {
ret=4
retry_times=$1 #接受傳入重試的次數,一般3次就行,
sql=$2 #sql語句
#如果引數不等於2,函式呼叫失敗
if [[ $# -ne 2 ]];then
echo "ERROR,parameters in function [retry_execute] are not correct,parameter number is $#"
exit 3
fi
echo "ready to execute $retry_times times,sql: $sql"
#利用for迴圈實現重試n次
for i in `seq 1 $retry_times`;do
echo "execute num: $i"
execute_sql "$sql" #呼叫execute_sql函式
ret=$?
if [[ $ret -ne 0 ]];then
#如果互動 mysql失敗,則睡一會,再重試。
echo "WARN: execute sql failed,sql: $sql,will sleep 10s and retry"
sleep 10s #失敗後多久重試的時間,這裡是10s
else #如果成功了直接break跳出迴圈
echo "execute sql success,sql: $sql"
ret=0
break
fi
done
return $ret
}
#呼叫retry_execute函式
retry_execute $RETRY_TIMES "${SELECT_SQL}"
SELECT_RET=$? #記錄這次呼叫的狀態並賦值給SELECT_RET
#呼叫retry_execute函式,實現修改原來的end_date=99991231的記錄
retry_execute $RETRY_TIMES "${UPDATE_SQL}"
UPDATE_RET=$? #記錄這次呼叫的狀態並賦值給UPDATE_RET
#判斷更新語句是否成功
if [[ $UPDATE_RET -ne 0 ]];then
echo "ERROR: update mysql failed $RETRY_TIMES times,will exit"
exit 9
fi
#呼叫retry_execute函式,實現insert新記錄記錄
retry_execute $RETRY_TIMES "$INSERT_SQL"
INSERT_RET=$?
#判斷insert語句是否成功
if [[ $INSERT_RET -ne 0 ]]; then
echo "ERROR: insert mysql failed with $RETRY_TIMES times,will exit"
exit 8
fi
echo "job complete"
最終的呼叫指令碼:
#123456位你mysql的密碼
#3為你重試次數
#20200301為新的資料檔案來的日期
./cfs_sale_his.sh 123456 3 20200301