1. 程式人生 > 資料庫 >Shell語言操控Mysql實戰——時間歷史拉鍊表的實現

Shell語言操控Mysql實戰——時間歷史拉鍊表的實現

介 紹

  Shell語言在Linux上有自己獨到的優勢,特別是在Job排程層面,為了排程專案的規範,往往我們都喜歡把配置寫在資料庫內或者xml檔案上,這裡就舉列實戰下Shell語言和MySQL資料庫的互動,以實現經典的時間歷史拉鍊表(資料倉庫也把這種邏輯叫作緩慢漸變維)為例。

專案背景

  很多情況下,新的資料不是固定時間如T+1生成的,可能是一週的某一天,或者是一個月的某一天,但是時間不固定,這個時候資料多了,就是工程師自己也無法知道某一時刻該用那一份資料,這個時候歷史拉鍊表就是一種解決辦法;
在這裡插入圖片描述

圖1 歷史拉鍊表業務邏輯

  如圖1,鬼谷資產公司,該公司有很多資產,但是有一些可能沒有政府備案,有一些壞了,有一些特殊原因不讓賣,於是不定期銷售部會產生一個可售資源的資料檔案,把符合銷售規範的資料標出來,銷售人員根據這份資料檔案內的資料對外銷售,如20200102除了資料檔案1,則沒有新的資料檔案出來前,銷售一直按這份資產資料對外銷售,直到20200115日後,新的資料檔案2出來了,則改用資料檔案2,以此類推,那麼回顧歷史資料分析,任意哪天event_day,該用那份資料呢?請看時間歷史拉鍊表的實現。

  mysql的DDL語句如下,其中create_date為資料檔案生成日期,end_date為資料檔案結束日期,my_partition是檔案的儲存分割槽,event_datetime為生成這條歷史拉鍊記錄的時間,這樣你只要任意一天mydate,你的邏輯mydate>=create_date and mydate<end_date,取出來的my_partition就是你任意一天資料檔案儲存的分割槽,通過時間歷史拉鍊表起到一個書本的目錄作用,去把任意一天需要的分割槽資料取出來,問題得到解決。

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