【一般】東京都オープンデータソースAPIを活用してみる（データ書込み編）

これまで**基礎準備編**、**データ取得編**と記事にしてきましたが、今回はAPIエンドポイントから取得し、JSONファイルとして保存されたデータをデータベースに書込む処理の自動化を行います。
前回と同じくSharperlightスケジューラのタスクとして作成します。
### データ書込みタスク ###
[Sharperlight](https://sharperlight.jp/)アプリケーションメニューから**スケジューラ**を起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/1a2be11e-a8c0-2d65-767c-a9bca212844d.png)
スケジューラが起動したら、`新規`ボタンで新しいタスクを追加します。
**コード**、**グループ**、**説明**等を記入します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/f7dc7701-c232-1527-7959-5cff91835ca7.png)
`適用`ボタンで一度保存しましょう。
`アクション`タブに移動します。`新規`アイコンでアクション一覧を表示し**JavaScript**アクションを追加します。
**JavaScript**アクションが追加されるとコード編集領域が下部に表示されるので、以下のコードを貼り付けます。
```
var workfolder = lib_task.ParamGet("WorkFolder");
var maxPages = lib_task.ParamGet("MaxFiles");
var chunkLimit = 100;
    
//=======================================================================================================
// 前処理
//=======================================================================================================
// データモデルのテーブル情報を取得
var wb = JSON.parse(lib_app.writeback.MetaDataJsonGet("TOKYOAPI", "Covid19Patient"));
if (wb.errorMessage.length > 0) {
    lib_task.LogMessage("Error: " + wb.errorMessage);
    pages = maxPages;
}

// データモデルのテーブル情報からフィールドのコードと実SQLフィールド名を抜き出す
var datamodelFieldInfo = [];
for (var wbcol in wb.columns) {
    let info = {
        code: wb.columns[wbcol].fieldCode.slice(1),
        sqlName: wb.columns[wbcol].sqlAttr.replace("[", "").replace("]", "")
    }
    datamodelFieldInfo.push(info);
}



//=======================================================================================================
// JSONファイルをひとつひとつ処理する
//=======================================================================================================
var pages = 0;
var batch = {
    items: []
}
batch.items.push(wb);

while (pages < maxPages){
    if (lib_task.state.abort ) { break; }
    
    pages++;
    

    // ファイルからデータを読む
    var filePath = workfolder + "stagingResult_" + pages.toString() + ".json";
    //_taskData.log.debug(_taskData,"===>: " + filePath);
    if(!lib_sys.io.file.Exists(filePath)){
        lib_task.LogMessage("FINISH, No more data");
        break;
    }
    try {
        var file = lib_sys.io.file.ReadAllText(filePath);
    } catch (err) {
        lib_task.LogMessage("Error: " + err);
        break;
    }

    // ファイルが空っぽの場合は、無視する
    if (file.length == 0) continue;
    
    // ファイルの中身を得る
    fileContent = JSON.parse(file);
    lib_task.LogMessage("Recs=" + fileContent[0].length);
    if (fileContent[0].length == 0){ 
        lib_task.LogMessage("Warning: No contents");
        continue; 
    }
    
    
    //=====================================================================================================
    // データをデータベースに書き込んでいく
    //=====================================================================================================
    var dataFields;
    var cells = [];
    var chk = 0;
    var found = false;
    wb.rows = [];
    
    // レコードをひとつひとつ処理していく
    for (var r=0; r<fileContent[0].length; r++) {
        if (lib_task.state.abort) { break; }
        
        // レコードの取り出し
        var record = fileContent[0][r];
    
        chk++;
        cells = [];
        // レコードからフィールド名を取得
        dataFields = Object.keys(record);
        
        // 書き込み用オブジェクトを作成する (Sharperlight特有)
        for (var i in datamodelFieldInfo){
            found = false;
            if (datamodelFieldInfo[i].code == "Row_ID") continue;
            
            for (var f in dataFields) {
                if (dataFields[f].indexOf(datamodelFieldInfo[i].sqlName) > -1){ // データモデルで定義しているSQLフィールド名とレコードのフィールド名が一致
                    console.log(datamodelFieldInfo[i].sqlName + "/" + dataFields[f]);
                    cells.push({
                        "code": datamodelFieldInfo[i].code,　// データモデルで定義しているフィールドコード
                        "value": _convert_ISODate(record[dataFields[f]]) // レコードの実データ
                    });
                    found = true;
                    break;
                }

            }

            // データモデルで定義しているフィールドがレコード内に存在しなかった場合
            if (!found){
                if (datamodelFieldInfo[i].code.indexOf("Date") > -1){
                    cells.push({
                        "code": datamodelFieldInfo[i].code,
                        "value": _convert_ISODate("1900-01-01")
                    });
                }else{
                    cells.push({
                        "code": datamodelFieldInfo[i].code,
                        "value": _convert_ISODate("-")
                    });
                }
            }
        }

        console.log(cells.length + " cols processed");
        wb.rows.push({
            "cells": cells
        });
        console.log(wb.rows.length + " rows processed");

        // 変数chunkLimitで指定した複数のレコードをまとめて書き込む
        if (chk == chunkLimit) {
            wb.mode = 'Update';
            wb.breakOnFirstRowError = true;
            var wbStatus;
            try {
                wbStatus = JSON.parse(lib_app.writeback.ExecuteBatch(batch));
            } catch (err) {
                lib_task.LogMessage("Warning: " + err.message + "/ " + JSON.stringify(wb.rows));
                wb.rows = [];
                chk = 0;
                continue;
            }

            if (wbStatus.errorMessage.length > 0) {
                var message = "ERROR ON: " + '\n';
                for (var c = 0; c < wbStatus.status.length; c++) {
                    var si = wbStatus.status[c];
                    message += (si.message + '\n' + JSON.stringify(wb.rows) + '\n');
                }
                console.log(message);
            }
            wb.rows = [];
            chk = 0;
        }
    }

    // 書き込みが終わっていないデータを書き込む
    if (wb.rows.length > 0) {
        wb.mode = 'Update';
        wb.breakOnFirstRowError = true;
        var wbStatus;
        try {
            wbStatus = JSON.parse(lib_app.writeback.ExecuteBatch(batch));
        } catch (err) {
            lib_task.LogMessage("Warning: " + err.message + "/ " + JSON.stringify(wb.rows));
            wb.rows = [];
            chk = 0;
            continue;
        }

        if (wbStatus.errorMessage.length > 0) {
            var message = "ERROR ON: " + '\n';
            for (var c = 0; c < wbStatus.status.length; c++) {
                var si = wbStatus.status[c];
                message += (si.message + '\n' + JSON.stringify(wb.rows) + '\n');
            }
            console.log(message);
        }
        wb.rows = [];
        chk = 0;
    }
}

function _convert_ISODate(xValue) {
    if (!/^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)+([0-9]+)?$/g.test(xValue)) {
        if (!/^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])?$/g.test(xValue)) {
            return xValue;
        } else {
            return (xValue.replace('0000-', '1900-'));
        }
    } else {
        var xDate = new Date(xValue);
        return (xDate.toLocaleString());
    }
}
```
コード中に登場するこのような関数は、Sharperlightエンジンが提供する関数群です。
lib_task.ParamGet()、lib_app.writeback.ExecuteBatch()、lib_sys.io.file.ReadAllText()など

このコードは、前のタスクで指定された作業フォルダに保存されたJSONファイルを読み込んで、Sharperlightデータモデルの定義を利用し、接続しているデータベースの対象テーブルに、指定したレコード数を一括書き込みするコードです。この処理は作業フォルダ内の全JSONファイルに対して繰り返し行われます。

コードのテストは、`接続`テストボタンで行うことができます。またタスクをOKボタンで閉じた後、タスク一覧の右クリックメニューから`実行`を選択する事でも行えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/cad68e55-c64f-3acc-efaf-6ddf90ca90f8.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/e2f07444-a6c3-41b2-67d8-6fe6df246fa8.png)

タスクのパラメータとして作業フォルダ名と最大処理ファイル数を渡すようにしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/cadc8feb-6163-5c8a-6a7c-85b9108cebff.png)
以上で書込みタスクの作成は完了です。
### データ取得タスクとデータ書込みタスクの連携 ###
前回作成したデータ取得タスクが実行された直後、データ書込みタスクを実行したいです。なので`親タスク`を作ってその二つのタスクを`子タスク`として順番に呼ぶようにします。
新規ボタンで新しいタスクを追加します。
**コード**、**グループ**、**説明**等を記入し、`適用`ボタンで保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/c6941250-e9c8-ae41-3c08-582d07427f66.png)
`アクション`タブに移動し、`新規`アイコンでタスク一覧を表示し、**指定タスクを実行**アクションを追加します。
実行するタスクを選択します。最初は**データ取得**タスクで、次が**データ書込み**タスクです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/d9d967f3-beda-98d0-b5ac-802528377697.png)
`適用`ボタンで保存します。
`トリガー`タブに移動し、実行時間を設定します。
`新規`アイコンで新しいトリガーを追加します。`タイプ`属性に**サービスタイマー-開始/毎週/毎月**を選びます。`サービスタイマー`属性では、**開始**を選び、**開始時刻**を**朝6時**に設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/d9ec243f-721b-820b-5245-72686e50b74a.png)
`OK`ボタンで保存して閉じます。これでSharperlightサービスが起動していれば毎朝6時にこのタスクは実行されます。

タスクが正常に実行されると、データベーステーブルはこのようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/27902256-2b2d-6b5a-f2c8-1c2c3b552780.png)

これでAPIエンドポイントからデータを取得して、データベースに書込むことができました。あとはSharperlightのレポーティング機能を利用してお好みのレポートを作成することができます。

では、失礼します。