mikan's technical note

仕事&趣味で実験した小技の備忘録です(Linux,windows,DOS等)

MENU

【shell】毎日指定時間内に動的にSQLを生成して実行する

21:00:00~23:59:59 の間、処理を繰り返したい場合の例

resident_main.sh

#!/bin/bash

############################################################
#
# 毎日指定時間内にSQL実行(常駐)
# (c) 2018 mikan
# ※使用にあたっては利用者の自己責任でお願いします。
#  使い方:
#          例)nohup bash resident_main.sh &
#
#     ログファイル名:yyyymmdd_HHMMSS_sql_main.log
#
############################################################

### ----- 初期処理
LOG_FILE_NAME=`date +%Y%m%d_%H%M%S`_sql_main.log

### ----- 終了フラグ
END_FLG="end.flg"

### ----- スリープ時間(1~60秒程度で設定)
SLEEP_TIME=2

### ----- 進捗表示用(ASCIIコードの16進) debug用
#CODE=0 #debug

SYSTEM_DATE=`date +%Y/%m/%d_%H:%M:%S`
echo "※21:00~17:59の間はスリープします" >> $LOG_FILE_NAME
echo "※中断したい場合は、touch $END_FLG" >> $LOG_FILE_NAME
echo "[$SYSTEM_DATE]: ===== SQL実行メイン(常駐) - 開始 =====" >> $LOG_FILE_NAME

# ----- 終了フラグを削除
if [ -f `pwd`/$END_FLG ]
then
  rm `pwd`/$END_FLG
fi

### ----- 処理完了日
DAY_COMPLETE=00000000

### ----- DAY_COMPLETE を外部ファイルへ出力(グローバル変数として使用)
echo "DAY_COMPLETE=$DAY_COMPLETE;export DAY_COMPLETE" > day_complete.dat

### ----- メインループ
while :
do
  # ----- ※00:00~20:59の間はスリープ
  while :
  do
    # ----- 終了チェック
    if [ -f `pwd`/$END_FLG ]
    then
      SYSTEM_DATE=`date +%Y/%m/%d_%H:%M:%S`
      echo "[$SYSTEM_DATE]: $END_FLG 検出!" >> $LOG_FILE_NAME
      echo
      echo "[$SYSTEM_DATE]: ===== SQL実行メイン(常駐) - 終了 =====" >> $LOG_FILE_NAME
      echo
      exit 0;
    fi

    # ----- 時刻チェック
    S_TIME=`date +%H` # 本番 (時)

    case $S_TIME in
      21 | 22 | 23 ) #21時~23時(21:00:00~23:59:59)の場合はループを抜ける
         break;;
      * ) #実行時間外
         sleep $SLEEP_TIME
         continue;;
    esac

    sleep $SLEEP_TIME
  done

  # ----- DAY_COMPLETE を取得
  . ./day_complete.dat

  # ----- システム日付(yyyymmdd)を取得
  DAY_TODAY=`date +%y%m%d`

  #----- 1日1回だけSQL実行を呼び出す
  if [ $DAY_TODAY != $DAY_COMPLETE ]
  then
    # ----- SQL実行呼び出し
    SYSTEM_DATE=`date +%Y/%m/%d_%H:%M:%S`
    echo "[$SYSTEM_DATE]: SQL実行を呼び出し" >> $LOG_FILE_NAME
    bash ./sqlexe.sh

    # ----- 処理完了日へシステム日付(yyyymmdd)を取得
    DAY_COMPLETE=`date +%y%m%d`

    # ----- DAY_COMPLETE を外部ファイルへ出力(グローバル変数として使用)
    echo "DAY_COMPLETE=$DAY_COMPLETE;export DAY_COMPLETE" > day_complete.dat

    SYSTEM_DATE=`date +%Y/%m/%d_%H:%M:%S`
    echo "[$SYSTEM_DATE]: DAY_COMPLETE(処理完了日)=$DAY_COMPLETE" >> $LOG_FILE_NAME
  fi

  sleep $SLEEP_TIME
done

# ----- 理論上ここを通ることはない
exit 1

resident_sql.sh

#!/bin/bash

############################################################
#
# 動的にSQLを生成して実行
# (c) 2018 mikan
# ※使用にあたっては利用者の自己責任でお願いします。
#
#   ログファイル名:yyyymmdd_HHMMSS_sql_exec.log
#
#   SQL文だけを抽出する例
#     grep -v ": " 20180515_215302_sql_exec.log
#
#   SQL文以外の実行ログを抽出する例
#     grep ": " 20180515_215402_sql_exec.log
#
############################################################

### ----- ログファイル
LOG_FILE_NAME=`date +%Y%m%d_%H%M%S`_sql_exec.log

### ----- SQL実行間隔(1~5秒程度で設定)
SLEEP_TIME=5

### ----- 終了フラグ
END_FLG="end.flg"

### ----- パラメータファイル取込
. ./param.txt

echo
SYSTEM_DATE=`date +%Y/%m/%d_%H:%M:%S`
echo "[$SYSTEM_DATE]: ===== SQL実行 - 開始 ====="

### ----- メインループ
for i in "${dateArray[@]}";
do

  # ----- 終了チェック
  if [ -f `pwd`/$END_FLG ]
  then
    echo "$END_FLG 検出!"
    echo
    SYSTEM_DATE=`date +%Y/%m/%d_%H:%M:%S`
    echo "[$SYSTEM_DATE]: ===== SQL実行 - 終了 =====" >> LOG_FILE_NAME
    echo
    exit 0;
  fi

  # ----- 時刻チェック(18,19,20時以外は終了)
  S_TIME=`date +%H` # 時

  case $S_TIME in
    21 | 22 | 23 ) #21時~23時(21:00:00~23:59:59)の場合は処理
       ;;
    * ) #実行時間外(処理終了)
       break;;
  esac

  # ----- 各項目を取得
  data=(${i[@]})
  EXEC_DAY=${data[0]}
  DATE_STR=${data[1]}
  DATE_END=${data[2]}

  # ----- インクリメント
  COUNT=$(( COUNT + 1 )) #初期値はゼロ(初期化不要)

  # ----- 処理日チェック
  S_TIME=`date +%Y%m%d`
  echo "[$COUNT]: システム日付[$S_TIME] パラメータ[$EXEC_DAY $DATE_STR $DATE_END]" >> $LOG_FILE_NAME
  if [ $S_TIME != $EXEC_DAY ]
  then
    echo "[$COUNT]: 本日対象外の処理日行($EXEC_DAY)はスキップ" >> $LOG_FILE_NAME
    continue
  fi

  # ----- 条件表示
  S_YEAR=${DATE_STR:0:4}
  S_MON=${DATE_STR:4:2}
  S_DAY=${DATE_STR:6:2}
  S_NO=${DATE_STR:8}
  E_YEAR=${DATE_END:0:4}
  E_MON=${DATE_END:4:2}
  E_DAY=${DATE_END:6:2}
  E_NO=${DATE_END:8}

  # ----- SQLファイル削除
  if [ -f `pwd`/tmp.sql ]
  then
    rm `pwd`/tmp.sql
  fi

  # ----- SQLファイル作成ループ ※SQLは目的に合わせて書き換えてください
  for j in "${tableArray[@]}";
  do
    # ----- 各項目を取得
    tableName=(${j[@]})

    # ----- SQL
    echo "DELETE from $tableName" >> tmp.sql
    echo "  WHERE 項目名1 BETWEEN '$DATE_STR' AND '$DATE_END'" >> tmp.sql
    echo "    AND 項目2 IN ('AAA','BBB','CCC');" >> tmp.sql
    echo "commit work;" >> tmp.sql
  done;

  # ----- SQL実行 ※xxDBは環境に合わせて書き換えてください
  SYSTEM_DATE=`date +%Y/%m/%d_%H:%M:%S`
  echo >> $LOG_FILE_NAME
  echo "----- SQL start [$COUNT][$SYSTEM_DATE] -----" >> $LOG_FILE_NAME
  date >> $LOG_FILE_NAME
  rdbexecsql -s xxDB -i `pwd`/tmp.sql >> $LOG_FILE_NAME
  date >> $LOG_FILE_NAME
  SYSTEM_DATE=`date +%Y/%m/%d_%H:%M:%S`
  echo "----- SQL end   [$COUNT][$SYSTEM_DATE] -----" >> $LOG_FILE_NAME
  echo >> $LOG_FILE_NAME

  # ----- 終了チェック
  if [ -f `pwd`/$END_FLG ]
  then
    echo "$END_FLG 検出!" >> $LOG_FILE_NAME
    break;
  fi

  # ----- スリープ
  sleep $SLEEP_TIME
done;

SYSTEM_DATE=`date +%Y/%m/%d_%H:%M:%S`
echo "[$SYSTEM_DATE]: ===== SQL実行 - 終了 =====" >> LOG_FILE_NAME
echo

exit 0

param.txt

### ----- 処理完了日(debug等特殊な場合を除き通常は"00000000"を設定)
DAY_COMPLETE=00000000;export DAY_COMPLETE

### ----- 対象テーブル
tableArray=(
'xxxx.テーブル1'
'xxxx.テーブル2'
'xxxx.テーブル3'
)
export tableArray

### ----- BETWEEN 条件
#
# システム日付と処理日が一致する行だけが
# 実行対象となります
# 一致しない行は、スキップします
#
#-処理日- -----開始------ -----終了------
dateArray=(
'20180516 2008010100000 2008123199999'
'20180516 2009010100000 2009123199999'
'20180517 2010010100000 2010123199999'
'20180517 2011010100000 2011123199999'
'20180518 2012010100000 2012123199999'
'20180518 2013010100000 2013123199999'
)
export dateArray

【C言語】SymfowareのSELECT結果をCSV形式に変換する

//
// SymfowareのSELECT結果をCSV形式へ変換 ※Solarisで動作確認済(1行32kまで対応)
// (c) 2018 mikan
// ※使用にあたっては利用者の自己責任でお願いします。
//
// 使い方:SQLtoCSV input [output]
//         例)SQLtoCSV select.log
//         例)SQLtoCSV select.log select.txt
//
//         おまけ機能)INSERT SQL を作成する方法
//         SQLtoCSV select.log select.txt -insert
//

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

#define BUFF_MAX    32768    // 入力ファイルの1行の最大サイズ
#define FIELD_MAX    1024    // 項目の最大数

int MakeCsvHead(char *, char *, int);
int MakeCsvData(char *, char *, int, int, int);

char fullpath1[2048];
char fullpath2[2048];

int main(int argc, char **argv) 
{
    int        len;
    int        head_count;
    int        data_count;
    int        dc_flag;
    int        sp_flag;
    int        insert_flag;
    struct     stat    st;
    char       work[1024];

    dc_flag = 1;
    sp_flag = 0;
    insert_flag = 0;

    if(argc <= 1 || argc >= 4) {
        if(argc == 4 && strcmp(argv[3], "-insert") == 0) {
            printf("-insert オプション検出! insert.sql を作成します\n");
            insert_flag = 1;
        } else {
            printf("パラメータが不正です\n");
            return -1;
        }
    }

    // ----- 入出力ファイル名の取得
    strcpy(fullpath1, argv[1]);

    // ----- 入力ファイルのチェック
    if(stat(fullpath1, &st) != 0) {
        printf("%s\n入力ファイルが存在しません\n", fullpath1);
        return -1;
    }

    // ----- 出力ファイル名の自動生成
    if(argc == 3) {
        strcpy(fullpath2, argv[2]);
    } else {
        strcpy(fullpath2, fullpath1);
        strcat(fullpath2, ".txt");
    }

    // ----- 入出力ファイル名のチェック
    if(strcmp(fullpath1, fullpath2) == 0) {
        printf("変換元/変換先ファイル名が同じ\n");
        return -1;
    }

    printf("SELECT結果変換 - 開始\n");

    // ----- CSVファイル作成
    head_count = MakeCsvHead(fullpath1, fullpath2, dc_flag);
    data_count = MakeCsvData(fullpath1, fullpath2, dc_flag, sp_flag, insert_flag);

    printf("SELECT結果変換 - 完了っ[%d件] [%d項目]\n", data_count, head_count);
}

// NUMERICチェック(先頭が'+'または'-'で、
//                 後続が全て数値('0'-'9')または'.'で、
//                 '.'が1個の場合、0を返す)
int numeric_check(char *str)
{
    int        i;
    int        len;
    int        count;

    len = 0;
    count = 0;

    // 先頭が'+'でも'-'でもない場合は、NUMERIC型ではない
    if(str[0] != '-' && str[0] != '+') {
        return -1;
    }

    // 全桁チェック
    len = strlen(str);
    for(i=1; i < len ; i++) {
        if(str[i] == 0x00) {
            break;
        }

        // 数値でも'.'でもない場合は、NUMERIC型ではない
        if(str[i] < 0x30 || str[i] > 0x39) {
            if(str[i] == '.') {
                count ++;
            } else {
                return -1;
            }
        }
    }

    // '.'が1個でない場合は、NUMERIC型ではない
    if(count != 1) {
        return -1;
    }

    // 数値('+9999.' or '-9999.' or '+9999.9999' or '-9999.9999')
    return 0;
}

int DeleteSpace(char *str1, char *str2, int size)
{
    int    i;
    int    len;

    strcpy(str2, str1);

    for(i=0; i<size; i++) {
        if(str1[i] == 0x20) {
            strcpy(str2, &str1[i+1]);
        } else {
            break;
        }
    }

    len = strlen(str2);    
    for(i=len-1; i>0; i--) {
        if(str2[i] == 0x20) {
            str2[i] = 0x00;
        } else {
            break;
        }
    }

    len = strlen(str2);    
    return len;
}

int CheckRecord(char *buff, char *tablename)
{
    int     i;
    char    *p;

    // ----- テーブル名の取得(xxxx を 環境に合わせて書き換えてください)
    if((p = strstr(buff, "xxxx.")) != NULL) {
        for(i=0; ; i++) {
            if(p[i] == ' ' || p[i] == 0x0a) {
                memset(tablename, 0x00, sizeof(tablename));
                memcpy(tablename, p, i);
                break;
            }
        }
    }

    if(strncmp(buff, "Number of records", 17) == 0) {
        return -1;
    }
    if(strncmp(buff, "SQLSTATE", 8) == 0) {
        return -1;
    }
    if(strncmp(buff, "SQLMSG", 6) == 0) {
        return -1;
    }
    if(strncmp(buff, "SELECT", 6) == 0) {
        return -1;
    }
    if(strncmp(buff, "select", 6) == 0) {
        return -1;
    }
    if(strncmp(buff, "Select", 6) == 0) {
        return -1;
    }

    return 0;
}

// ----- CSVヘッダ作成処理
int MakeCsvHead(char *in_file, char *out_file, int dc_flag)
{
    FILE    *fp1, *fp2;        // ファイルポインタ
    int     i;                 // インデックス
    int     len;               // 文字列のサイズ取得変数
    int     count_record;      // 出力レコード数
    int     count_field;       // 出力フィールド数
    char    buff[BUFF_MAX];    // 入力ファイルの1行読込エリア
    char    work[BUFF_MAX];    // ワークエリア

    // ----- パラメータチェック
    if(in_file == NULL || out_file == NULL) {
        return(-1);
    }

    // ----- 入力ファイルオープン(読込み専用)
    fp1 = fopen(in_file, "r");
    if(fp1 == NULL) {
        return(-1);
    }

    // ----- 出力ファイルオープン(上書き)
    fp2 = fopen(out_file, "w");
    if(fp2 == NULL) {
        fclose(fp1);
        return(-1);
    }

    // ----- 初期化
    count_record = 0;
    count_field = 0;

    // ----- 入力ファイルの先頭レコードぶん繰り返し
    while(1) {
        if(count_field >= FIELD_MAX) {
            break;
        }

        // ----- 1行読込(EOFで抜ける)
        memset(buff, 0x00, sizeof(buff));
        if(fgets(buff, BUFF_MAX-1, fp1) == NULL) {
            break;
        }

        // ----- 読み飛ばし
        if(CheckRecord(buff, work) != 0) {
            continue;
        }

        // ----- 行末の改行コードを取る
        len = strlen(buff);
        buff[len-1] = NULL;

        // ----- <<xxx>> を探す。(レコードの始まり)
        if(strncmp(buff, "<<", 2) == 0) {
            count_record ++;
            if(count_record > 1) {
                break;
            }
            continue;
        }

        // ----- ファイル出力(ヘッダ部)
        for(i = 0; i<BUFF_MAX; i ++) {
            if(buff[i] == ':') {
                count_field ++;
                if(count_field > 1) {
                    fprintf(fp2, ",");
                }
                buff[i]=0x00;

                // 前後の空白削除
                len = strlen(buff);
                memset(work, 0x00, sizeof(work));
                DeleteSpace(buff, work, len);

                // ヘッダは強制的にダブルコーテーションなしとする
                fprintf(fp2, "%s", work);
                break;
            }
        }
    }

    fprintf(fp2, "\n");

    // ----- ファイルクローズ
    fclose(fp1);
    fclose(fp2);

    return(count_field);
}

// ----- CSVデータ作成処理
int MakeCsvData(char *in_file, char *out_file, int dc_flag, int sp_flag, int insert_flag)
{
    FILE    *fp1, *fp2;        // ファイルポインタ
    FILE    *fp3;              // ファイルポインタ
    int     i;                 // インデックス
    int     len;               // 文字列のサイズ取得変数
    int     count_record;      // 出力レコード数
    int     count_field;       // 出力フィールド数
    char    buff[BUFF_MAX];    // 入力ファイルの1行読込エリア
    char    work[BUFF_MAX];    // ワークエリア
    char    tname[BUFF_MAX];   // テーブル名

    // ----- パラメータチェック
    if(in_file == NULL || out_file == NULL) {
        return(-1);
    }

    // ----- 入力ファイルオープン(読込み専用)
    fp1 = fopen(in_file, "r");
    if(fp1 == NULL) {
        return(-1);
    }

    // ----- 出力ファイルオープン(追記)
    fp2 = fopen(out_file, "a");
    if(fp2 == NULL) {
        fclose(fp1);
        return(-1);
    }

    if(insert_flag == 1) {
        // ----- 出力ファイルオープン
        fp3 = fopen("insert.sql", "w");
        if(fp3 == NULL) {
            fclose(fp1);
            fclose(fp2);
            return(-1);
        }
    }

    // ----- 初期化
    count_record = 0;
    count_field = 0;

    // ----- 入力ファイルの全レコードぶん繰り返し
    while(1) {
        if(count_field >= FIELD_MAX) {
            break;
        }

        // ----- 1行読込(EOFで抜ける)
        memset(buff, 0x00, sizeof(buff));
        if(fgets(buff, BUFF_MAX-1, fp1) == NULL) {
            break;
        }

        // ----- 読み飛ばし
        if(CheckRecord(buff, tname) != 0) {
            continue;
        }

        // ----- 行末の改行コードを取る
        len = strlen(buff);
        buff[len-1] = NULL;

        // ----- <<xxx>> を探す。(レコードの始まり)
        if(strncmp(buff, "<<", 2) == 0) {
            count_record ++;
            count_field = 0;
            if(count_record > 1) {
                fprintf(fp2, "\n");
            }
            if(insert_flag == 1) {
                if(count_record >= 2) {
                    fprintf(fp3, ");\nCOMMIT WORK;\n");
                    fprintf(fp3, "INSERT INTO %s VALUES (", tname);
                } else {
                    fprintf(fp3, "INSERT INTO %s VALUES (", tname);
                }
            }

            continue;
        }

        // ----- ファイル出力(データ部)
        for(i = 0; i<BUFF_MAX; i ++) {
            if(buff[i] == ':') {
                count_field ++;
                if(count_field > 1) {
                    fprintf(fp2, ",");
                    if(insert_flag == 1) {
                        fprintf(fp3, ",");
                    }
                }

                // 前後の空白削除
                if(sp_flag == 1) {
                    len = strlen(&buff[i+1]);
                    memset(work, 0x00, sizeof(work));
                    DeleteSpace(&buff[i+1], work, len);
                } else {
                    strcpy(work, &buff[i+1]);
                }

                    if(dc_flag == 1) {
                    if(numeric_check(work) != 0) {
                        fprintf(fp2, "\"%s\"", work);
                        if(insert_flag == 1) {
                            fprintf(fp3, "'%s'", work);
                        }
                    } else {
                        fprintf(fp2, "%s", work);
                        if(insert_flag == 1) {
                            fprintf(fp3, "%s", work);
                        }
                    }
                } else {
                    fprintf(fp2, "%s", work);
                    if(insert_flag == 1) {
                        fprintf(fp3, "%s", work);
                    }
                }
                break;
            }
        }
    }

    fprintf(fp2, "\n");
    if(insert_flag == 1) {
        fprintf(fp3, ");\nCOMMIT WORK;\n");
    }

    // ----- ファイルクローズ
    fclose(fp1);
    fclose(fp2);
    if(insert_flag == 1) {
        fclose(fp3);
    }

    return(count_record);
}