【Scheduler】Using PayPal Rest API (Transaction:Writeback)

In the previous [article](https://qiita.com/Miki_Yokohata/items/33e16c98713d6e453ab5), we analyzed the retrieved recordset, flattened the hierarchical data, and determined what columns (and values) existed.

This time, let's create the part that writes data to the database table.
## Preparing the database ##
Since I already know the required columns, I create the table with the SQL CREATE TABLE statement.
Launch SQL Server Management Studio and create an empty database. Create a table in that database like this:
```
CREATE TABLE [dbo].[Transaction_Info](
	[paypal_account_id] [nvarchar](256) NULL,
	[transaction_id] [nvarchar](256) NOT NULL,
	[transaction_event_code] [nvarchar](256) NULL,
	[transaction_initiation_date] [datetime] NULL,
	[transaction_updated_date] [datetime] NULL,
	[transaction_amount_currency_code] [nvarchar](20) NULL,
	[transaction_amount_value] [decimal](36, 6) NULL,
	[transaction_status] [nvarchar](10) NULL,
	[ending_balance_currency_code] [nvarchar](20) NULL,
	[ending_balance_value] [decimal](36, 6) NULL,
	[available_balance_currency_code] [nvarchar](20) NULL,
	[available_balance_value] [decimal](36, 6) NULL,
	[protection_eligibility] [nvarchar](10) NULL,
 CONSTRAINT [PK_Transaction_Info_1] PRIMARY KEY CLUSTERED 
(
	[transaction_id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
```
## Creating the data model ##
### Import Schema ###
Now that I have the database and tables ready, let's create a data model using Sharperlight Studio.
Launch **Studio** from the [Sharperlight](https://sharperlight.com/) application menu.
Start creating with the `New` icon.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/5eeaefd6-22c9-63c3-a4ba-2841bb54e682.png)
Set the `Description` `Unique Code` `Target Platforms` and click the `OK` button.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/9be5b0b1-b4d7-60dd-d6c4-5cb2d60f2149.png)
Select a connection type and enter the required connection information. Check the connection with the `Test` button. This is a connection setting for incorporating database schema information into the data model.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/12d94ec5-21c7-18a9-7dcf-7a43af0c12b9.png)
If you press the `OK` button, a message will appear asking if you want to import the schema information, so press the `Yes` button to proceed.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/d3508161-2499-0c26-0860-6fa0aff11598.png)
In the **Import Objects** dialog, the `Get Tables` button initiates schema import.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/2b060931-7f8d-53ec-d781-0d1e6877f1d1.png)
Table and field information is retrieved from the connected database. I found two tables for the system (not used this time).
With all selected, press the `OK` button to start importing to the data model.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/e12fe800-6c3c-4004-1760-e3d6b72fc19e.png)
The engine will analyze and classify the table names like this. Table codes and field codes that are used as recognition codes in all Sharperlight applications are also automatically assigned.
You are free to change these codes and descriptions in the data model to whatever values you like during the data model development stage.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/432bc5f1-ca3b-badf-27c2-7fb748864ae2.png)
This is what it will look like in the end.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/a3ac8437-d6f1-d8c5-c024-3c58a28a43f4.png)
### Field Code ###
Here I am talking about changing field codes.
This data model can be used as a mapping between the acquired dataset and the database table in the process of acquiring the dataset from the API endpoint and writing it to the database like this time. Use field codes for that mapping.
In other words, set the column name of the dataset obtained from the API to the field code. This will result in a mapping like this:
Through the data model, the data set from the API and the database table are connected.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/9d8fea7e-9b22-0b0e-86bb-5af02e86759a.png)
The data model is now ready.
## Create scheduler task for writing ##
Now that the database and data model have been prepared, let's write a JavaScript task that writes the contents of the dataset to the database.
As for the logic, I would like to read the data set from the API saved as a JSON file one record at a time, dynamically create SQL statements using the data model mapping settings, and write the records to the database.
Refer to [Get](https://qiita.com/Miki_Yokohata/items/8afde60ce05713f715c1) for `Dataset from API saved as JSON file`.
See [Analysis](https://qiita.com/Miki_Yokohata/items/33e16c98713d6e453ab5) for the part of `reading one record at a time`.
This time, I will use the task created in the `Analysis` section. I have already flattened the records by reading each record from the JSON file using the query combination action, so we will use the definition information of the data model to create and execute the SQL statement. I used some of Sharperlight JavaScript functions to create the process. Please excuse the messy code below:bow_tone1:

Function to get data model definition
```
function metaDetail_Get(xObjectName){
    try{
        var filters = { 'TabCod': xObjectName };
        var rs = lib_app.query.Execute(_gMetaQueryTemplate_XML, filters);
        lib_sys.io.file.WriteAllText(_gWorkFolder + "\\metaDetail.json", rs);

        var rsObj = JSON.parse(rs);
        if( rsObj.status.errorMessage != '' ) {
            throw 'metaDetail_Get(): ' + rsObj.status.errorMessage;
        }
        return rsObj;
    }catch (err){
        throw 'metaDetail_Get(): ' + err;
    }
}
```
Function that retrieves the field code from the data model table definition
```
function get_KeyFields(metaObject){
    try{
        var keys = [];
        for (var wbcol in metaObject.rows) {
            if (metaObject.rows[wbcol].IsUni == true){
                keys.push(metaObject.rows[wbcol].FieCod.slice(1));
            }
        }
        return keys;
    }catch (err){
        throw 'get_KeyFields(): ' + err;
    }
}
```
Function that retrieves values from a dataset
```
function build_SQLValue(value, code, metaObject){
    for (var wbcol in metaObject.rows) {
        if (metaObject.rows[wbcol].FieCod.toLowerCase().slice(1) == code.toLowerCase()){
            switch(metaObject.rows[wbcol].DatTyp){
                case 'String' : return "N'" + lib_data.QuoteProtect(value) + "'" ; break;       
                case 'DateTimeOffset' : return "N'" + value + "'"  ; break;       
                case 'Guid' : return "N'" + lib_data.QuoteProtect(value) + "'" ; break;       
                case 'Boolean' : return value == false ? 0 : 1; break;       
                case 'Decimal' : return value == '' ? (metaObject.rows[wbcol].IsNul == true ? " null " : 0) : value; break;       
                case 'Currency' : return value == '' ? (metaObject.rows[wbcol].IsNul == true ? " null " : 0) : value; break;       
                case 'Integer' : return value == '' ? (metaObject.rows[wbcol].IsNul == true ? " null " : 0) : value; break;       
                case 'Int32' : return value == '' ? (metaObject.rows[wbcol].IsNul == true ? " null " : 0) : value; break;
                case 'Date':
                    if (value == ''){
                        var xDefault = new Date('1900-01-01 00:00:00');
                        return (metaObject.rows[wbcol].IsNul == true ? " null " : "N'" + xDefault.toISOString() + "'"); break;
                    }else{
                        var xDate = new Date(value);
                        myDateFormat = xDate.toISOString();
                        if (myDateFormat == '0001-01-01') 
                            return " null ";
                        else 
                            return "N'" + myDateFormat + "'";
                        break;
                    }
                case 'Stream': return "N'" + lib_data.QuoteProtect(value) + "'" ; break;       
                default : return value; break;
            }
        }
    }
    return null;
}
```
Function that build and execute SQL statements
```
function generate_run_SQLStatement(colObjects, metaObject){
    var sqlStatement = '';
    var fieldValuePairs = [];
    var sqlfields = [];
    var sqlvalues = [];
    var sqlwhere = "";
    var keyFields = get_KeyFields(metaObject);
    for (let col=0; col < colObjects.length; col++) {
        let sqlValue = build_SQLValue(colObjects[col].value, colObjects[col].code, metaObject);

        if (sqlValue != null){
            fieldValuePairs.push( "[" + colObjects[col].code + "] = " + sqlValue);
            sqlfields.push("[" + colObjects[col].code + "]");
            sqlvalues.push(sqlValue);

            for (key in keyFields){
                if(keyFields[key].toLowerCase() == colObjects[col].code.toLowerCase()){
                    if(sqlwhere == "")
                        sqlwhere += " WHERE [" + colObjects[col].code + "] = " + sqlValue;
                    else
                        sqlwhere += " and [" + colObjects[col].code + "] = " + sqlValue;
                }
            }
        }
    }
    console.log("WHERE clause: " + sqlwhere);


    var updateStatement = "UPDATE [dbo].[" + 'Transaction_Info' + "] set " + fieldValuePairs.join(" , ") + sqlwhere;
    sqlStatement = updateStatement + "\r\n IF @@ROWCOUNT=0 \r\n \
        BEGIN \r\n \
                insert into [dbo].[" + 'Transaction_Info' + "]  ( " + sqlfields.join(",") + " ) values ( " + sqlvalues.join(",") + " ); \r\n \
        END ";

    console.log("SQL clause: " + sqlStatement);
    
    //exec sql
    try{
        lib_app.sql.Execute(lib_task.UserSessionIdGet(),'PAYPAL', sqlStatement);  
    }catch (err){
        console.log("Error: " + err);
    }
}
```

The Main Processe
```
// Get DB Schema Info via Datamodel using Sharperlight query.
// データモデルから対象テーブルの定義情報をSharperlightクエリを利用して取得。
var metaObject = metaDetail_Get('TransInfo');

// Get a record from Query Combination
// クエリの組み合わせアクションから渡される1レコードを取得
var output = lib_task.QueryCombinationValueGet('trans_info');
var rec = JSON.parse(output);

// Get columns for the record
var colObjects = [];
var cols = Object.keys(rec);                // 列の取得, Get columns from a record

for (var c in cols){
    // 子情報の有無をチェック, Check the existence of child information
    if (typeof rec[cols[c]] !== 'object'){  // 通常の列、Normal Column
        console.log(cols[c] + " / " + rec[cols[c]]);
        let obj = {
            code: cols[c],
            value: rec[cols[c]]
        }; 
        colObjects.push(obj);
    }else{                                  // 子情報、Child Information
        var childRec = rec[cols[c]];
        var childRecCols = Object.keys(childRec);
        for (var cc in childRecCols){
            console.log(cols[c] + ': ' + childRecCols[cc] + " / " + childRec[childRecCols[cc]]);
            let obj = {
                code: cols[c] + '_' + childRecCols[cc],
                value: childRec[childRecCols[cc]]
            };
            colObjects.push(obj);
        }
    }
}

// Create SQL Statement and Writeback data to database
// SQL文を作成して、データベースに書き込む
generate_run_SQLStatement(colObjects, metaObject);
```
Paste the code here to complete.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/ea77d99c-22e3-e01c-ed1b-ceb441cc435e.png)
Save it with the `OK` button, and select `Execute` from the scheduler's right-click menu to run the process.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/20c6b012-39bc-bf78-ed43-6fe17a77f994.png)
If there are no errors, here you go! This is how the data was written to the database.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/2bd59c87-f99f-e36d-956c-be6e78a9e996.png)
## Afterword ##
I've written three articles about automating dataset acquisition from the API, and this time I've finally written data to the database.
As the next step,  I will edit the task that retrieves the data from the API a bit to include a delta update feature so that each time the task is run it only retrieves the updates since the last time. It actually looks for the latest update date from the data already stored in the database and passes that date to the `start_date` parameter when querying the API.

Up to here for this time.

Thank you very much.