kintoneコマンドラインツールでUPSERTしてみた

前回の「[kintoneコマンドラインツールでデータ登録してみた](http://qiita.com/ushiron/items/97dd7a8eff2f8a2ea0de)」では、kintoneの異なるアプリのデータを定期的に別のアプリにコピーする方法を紹介しました。今回はコピー（追加）ではなく、UPSERT（キーとなる値があったら更新、なかったら追加）する方法を紹介したいと思います。

# イメージ
![20160505-1-37_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/100494/227765f6-ed2e-ebc2-f84f-d8888e777a37.png)

kintoneにはUPSERTするAPIがないのですが、2016年5月にアップデートされた kintone API（[レコード番号以外のキー項目でレコードの更新](https://cybozudev.zendesk.com/hc/ja/articles/207862196)）を利用して次のようにINSERTとUPDATEを使い分けることにしました。

**INSERT**：
アプリB（コピー先）の最大KeyCode（アプリAのレコードID）より大きいレコードIDを持ったアプリAのデータを抽出してアプリBに登録する。
※アプリAからアプリBにコピーする想定で、他のツールや人の操作によるレコード追加は想定していません。

**UPDATE**：
アプリB（コピー先）のキー日時（レコードごとの処理した日時）の最大が、アプリAの更新日時以下のレコードIDを持ったアプリAのデータを抽出してアプリBに更新する。
※INSERTの後にUPDATEするため、INSERTするデータがUPDATEするデータにも含まれますが、kintone APIでは変更のないデータは更新しない仕様です。

# kintoneコマンドラインツールを用意する
「[kintoneコマンドラインツール](https://cybozudev.zendesk.com/hc/ja/articles/202708214)」を使います。このツールを使った過去の記事を紹介します。

- [kintoneコマンドラインツールでサブテーブルを操作してみた](http://qiita.com/ushiron/items/f7f66632710b9b0834c5)
- [kintoneコマンドラインツールでデータ登録してみた](http://qiita.com/ushiron/items/97dd7a8eff2f8a2ea0de)

# 2つのアプリを用意する
異なるドメインでkintoneアプリをそれぞれ 1つずつ用意します。
※同じドメイン内の異なるアプリ間でも動作します。
kintoneの環境を持っていない！という方、[cybozu.com developer network](https://cybozudev.zendesk.com/hc/ja/articles/200929540#step3)で無償の開発者ライセンスを取得しておきましょう。

### アプリの構成
#### アプリA

>| フィールドコード | フィールドタイプ |
|:-----------|:------------|
| TitleA      | 文字列（1行）|
| NumberA     | 数値        |
| UpdateDatetime  | 更新日時（組み込み）|

#### アプリB

>| フィールドコード | フィールドタイプ | 備考 |
|:-----------------|:------------|:------------|
| KeyCode          | 数値        | 重複なし（コピー元のレコードIDを保存） |
| KeyUpdateDatetime| 日時        | 処理した日時を保存 |
| TitleB           | 文字列（1行）|  |
| NumberB          | 数値        |  |

### レコード登録画面
（フィールド名とフィールドコードを同じにしています）
アプリA
>![img_20160203095822.png](https://qiita-image-store.s3.amazonaws.com/0/100494/c1313229-a499-e88e-ba22-79b66667e464.png)

アプリB
>![20160505-23-19_No-01.png](https://qiita-image-store.s3.amazonaws.com/0/100494/cf57368d-d564-56ae-da47-bef62825466b.png)

### kintoneコマンドラインツールに必要な情報
#### アプリA

>| パラメータ | 値              |ここで実験する値 |
|:----------|:----------------|:------------|
| -a        | アプリID        |85  ※IDはURLに表示されています。         |
| -d        | サブドメイン名   |**sample1**.cybozu.com  ※サブドメイン名は各自異なります。
| -t        | APIトークン     |De5Xb3TM1TDccdqEM20eP1dpesjuBLckOx6AyM4n ※アプリの管理画面から発行できます。|

#### アプリB

>| パラメータ | 値              |ここで実験する値 |
|:----------|:----------------|:------------|
| -a        | アプリID        |822  ※IDはURLに表示されています。         |
| -d        | サブドメイン名   |**sample2**.cybozu.com  ※サブドメイン名は各自異なります。
| -t        | APIトークン     |BoNFRmX0oDoKQMSrTiZC7hbEmb9QaZa1vYVfnyD4 ※アプリの管理画面から発行できます。|

それぞれの値は環境によって異なります。

# いよいよ本題！シェルスクリプトを実行してみよう
Linux環境の場合、次のシェルスクリプトをUTF-8で保存しましょう。
ここでのファイル名は「upsert.sh」にしておきます。


次の処理をシェルスクリプトで実現してみます。
1. アプリB（コピー先）から最大KeyCode（アプリAのレコードID）を取得する
2. 最大KeyCodeより大きいレコードIDを持ったアプリAのデータを抽出してアプリBに登録する
3. アプリB（コピー先）のキー日時（レコードごとの処理した日時）の最大を取得する
4. その最大キー日時がアプリAの更新日時以下のレコードIDを持ったアプリAのデータを抽出してアプリBに更新する

## 説明

 ```--- self setting ---```で囲まれた部分のみ変更しましょう。

 - ```### APP_A```内の変数にアプリAの情報を設定
 - ```### APP_B```内の変数にアプリBの情報を設定
 - ```### Mapping custom``` 内の変数にコピー元のフィールド:コピー先のフィールドを設定
    - TitleAからTitleBにコピーする際には、```TitleA:TitleB``` と書きます
    - ```$id```など「`$`」を使う際にはエスケープしましょう
    - コピーするフィールドに応じて増やしましょう    

## プログラム

```bash:upsert.sh
#!/bin/bash

# --------------- self setting ------------------
### character encoding
OUTPUT_TYPE="sjis"
#OUTPUT_TYPE="utf-8"

### APP_A
APP_ID_A="85"
SUB_DOMAIN_A="sample1"
API_TOKEN_A="De5Xb3TM1TDccdqEM20eP1dpesjuBLckOx6AyM4n"
KEY_DATETIME_A="UpdateDatetime"

### APP_B
APP_ID_B="822"
SUB_DOMAIN_B="sample2"
API_TOKEN_B="BoNFRmX0oDoKQMSrTiZC7hbEmb9QaZa1vYVfnyD4"
UNIQUE_B="KeyCode"
KEY_DATETIME_B="KeyUpdateDatetime"
QUERY_B_INSERT="$UNIQUE_B != \"\" order by $UNIQUE_B desc limit 1"
QUERY_B_UPDATE="$UNIQUE_B != \"\" order by $KEY_DATETIME_B desc limit 1"

# Mapping
declare -a ARR_MAPPING=()
# Mapping define
ARR_MAPPING+=("\$id:KeyCode")
ARR_MAPPING+=("UpdateDatetime:KeyUpdateDatetime")
### Mapping custom
ARR_MAPPING+=("TitleA:TitleB") #sample:\$id,Title,Number,...
ARR_MAPPING+=("NumberA:NumberB")
# --------------- self setting ------------------

# Set cli-kintone files
CLI_FILES="cli-kintone"
BASE_DIR=$(cd $(dirname $0); pwd)
if [ ! -f "$BASE_DIR/$CLI_FILES" ]; then
    echo "Not found $CLI_FILES files."
    exit 1
fi
# Create files dirctory
OUTPUT_DIR="files"
BASE_DIR=$(cd $(dirname $0); pwd)
if [ ! -e "$BASE_DIR/$OUTPUT_DIR" ]; then
    mkdir -p "$BASE_DIR/$OUTPUT_DIR"
fi

# Set header fields
declare -a ARR_FIELDS_A=()
declare -a ARR_FIELDS_B=()
for i in "${ARR_MAPPING[@]}"
do
   ARR_FIELDS_A+=(`echo $i | cut -d":" -f1 | cut -d":" -f1`)
   strB=\"`echo $i | cut -d":" -f2 | cut -d":" -f1`\"
   ARR_FIELDS_B+=($strB)
done
OUTPUT_FIELDS="$(IFS=,; echo "${ARR_FIELDS_A[*]}")"
INSERT_FIELDS="$(IFS=,; echo "${ARR_FIELDS_B[*]}")"
UPDATE_FIELDS="$(IFS=,; echo "${ARR_FIELDS_B[*]}")"
# Set update field flg
UPDATE_FIELDS=`echo $UPDATE_FIELDS | sed -e "s/\"$UNIQUE_B\"/\"*$UNIQUE_B\"/"`

# Set datetime file
DATETIME=`date +%Y%m%d%H%M%S.%2N`
OUTPUT_FILE="output$DATETIME.csv"
OUTPUT_PATH=$BASE_DIR/$OUTPUT_FILE

# ---Get Update datetime Script----------------------
# Get max data
MAX_LIST=(`./$CLI_FILES -a $APP_ID_B -d $SUB_DOMAIN_B -t $API_TOKEN_B -e $OUTPUT_TYPE -c "$KEY_DATETIME_B" -q "$QUERY_B_UPDATE"`)
MAX_DATA=$(echo ${MAX_LIST[1]} | sed s/\"//g)
if [ -n "$MAX_DATA" ] ; then
    QUERY_A_DATE="$KEY_DATETIME_A > ${MAX_LIST[1]}"
fi
echo $QUERY_A_DATE

# ---Insert Script------------------------------------
# Get max data
MAX_LIST=(`./$CLI_FILES -a $APP_ID_B -d $SUB_DOMAIN_B -t $API_TOKEN_B -e $OUTPUT_TYPE -c "$UNIQUE_B" -q "$QUERY_B_INSERT"`)
MAX_DATA=${MAX_LIST[1]}
if [ -z "$MAX_DATA" ] ; then
    MAX_DATA="0"
    echo "Changed ID."
fi
MAX_DATA=`echo $MAX_DATA | sed s/\"//g`
QUERY_A="\$id > $MAX_DATA"
echo $QUERY_A

# Excute output file
./$CLI_FILES -a $APP_ID_A -d $SUB_DOMAIN_A -t $API_TOKEN_A -e $OUTPUT_TYPE -c "$OUTPUT_FIELDS" -q "$QUERY_A" >> $OUTPUT_FILE

# Change csv header
INSERT_FILE="Insert$DATETIME.csv"
find . -name "$OUTPUT_FILE" | sed -i -e "1,1d" $OUTPUT_FILE
find . -name "$OUTPUT_FILE" | sed -i -e "1i $INSERT_FIELDS" $OUTPUT_FILE
find . -name "$OUTPUT_FILE" | mv "$OUTPUT_FILE" "$INSERT_FILE"
echo "Insert file: $INSERT_FILE"

# Excute import file
./$CLI_FILES -a $APP_ID_B -d $SUB_DOMAIN_B -t $API_TOKEN_B -e $OUTPUT_TYPE -f "$INSERT_FILE"
mv "$INSERT_FILE" "$OUTPUT_DIR"
echo "Finished insert data."

# ---Update Script------------------------------------
# Excute output file
./$CLI_FILES -a $APP_ID_A -d $SUB_DOMAIN_A -t $API_TOKEN_A -e $OUTPUT_TYPE -c "$OUTPUT_FIELDS" -q "$QUERY_A_DATE" >> $OUTPUT_FILE

# Change csv header
UPDATE_FILE="Update$DATETIME.csv"
find . -name "$OUTPUT_FILE" | sed -i -e "1,1d" $OUTPUT_FILE
find . -name "$OUTPUT_FILE" | sed -i -e "1i $UPDATE_FIELDS" $OUTPUT_FILE
find . -name "$OUTPUT_FILE" | mv "$OUTPUT_FILE" "$UPDATE_FILE"
echo "Update file: $UPDATE_FILE"

# Excute import file
./$CLI_FILES -a $APP_ID_B -d $SUB_DOMAIN_B -t $API_TOKEN_B -e $OUTPUT_TYPE -f "$UPDATE_FILE"
mv "$UPDATE_FILE" "$OUTPUT_DIR"
echo "Finished update data."

```

## シェルスクリプトを実行してみる

```bash:
$ ./upsert.sh
```

**できた！！**
処理したファイルは、filesフォルダにそれぞれ Insert~.csv、Update~.csv というファイルで、保存しています。
![20160505-23-12_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/100494/b14b5ce6-0362-04b9-e82b-69cd2a2a71bb.png)


## さいごに
いかがでしたでしょうか。kintoneコマンドラインツールとシェルスクリプトを使うと **定期的に、別会社（ドメイン）へデータを同期して活用** することができますね。お試しください。