Pleasanter でリンクした項目をインポートするときの問題への対応
2022 個人アドベントカレンダー の記事です。
課題感
↓ をコードで無理矢理対応する
制限事項
「項目連携」を設定している「テーブル」で「インポート」を行うと親子関係が正しく登録できない場合があります。「インポート」では表示名に重複がある場合、「項目連携」の設定に関わらず最初に見つかったデータを紐づけます。「項目連携」の子「項目」の「表示名」に重複がある場合、親「項目」の値と関連の無い子「項目」の値がセットされる可能性があります。親「項目」の値と関連の無い子「項目」の値がセットされた場合、「エディタ」を開くと子「項目」の値がクリアされ未入力の状態となります。
ちょっとなに言ってるか分からない
どういう機能のどういう問題?
基本仕様
- 複数のテーブルに親子関係をつけることで、プルダウン(分類型項目)で "大分類" > "小分類" のような段階的な絞り込み(プルダウンの連動)を実現することができる。
- これが「項目連携」の機能
- 項目連携のプルダウンでは、連携した外部テーブル(マスタテーブル)のタイトルの値が入る
- 項目連携したとき、内部データには連携先のテーブルのレコード ID が保持されている
- 項目連携の表示値 →ID の置換は、CSV の取り込みのときにも働くので、CSV に表示値が入っていると ID 値に置換されて保持される
ここまでがベースになる仕様です。
問題が起こる状況
マニュアルの例の都道府県と市町村で次のようなデータを仮定します。
- 都道府県マスタ
- 北海道
- 福島県
- 東京都
- 広島県
- 市町村マスタ
- 伊達市 ← 北海道に紐付け
- 伊達市 ← 福島県に紐付け
- 府中市 ← 東京都に紐付け
- 府中市 ← 広島県に紐付け
このマスタを参照する住所録的なデータで ↓ のようなデータを取り込んだとき、次のような問題が発生します。
北海道,伊達市
福島県,伊達市
東京都,府中市
広島県,府中市
- 一覧表では正しく取り込めて見える
- 北海道伊達市をインポートしたデータを編集画面で見ると、市町村データが消える
開いた時点で消えてます - (↑ 消えたことに気付かず)更新すると、データが消えます。
(一覧からも消えます) - 同じことが広島県府中市でも発生します。
でも、福島県伊達市と東京都府中市では再現しない。
県に対して市名がユニークであるこの余の 788 市でも発生しない。
混乱を招く点として、データの作られかた次第では、北海道伊達市が正常で、福島県伊達市が異常である場合も起こり得ます。
動作
このようになる動作原理は次のとおりです。
- 先のとおり伊達市のデータは市町村テーブルの ID で保持されている
- CSV からのデータ取り込みにおいて、項目連携は考慮されず、各テーブルのタイトル表示値だけを基準に ID 置換が行われる
- そのため伊達市は、都道府県カラムのデータを無視して、常に特定の ID に解決される(ここがデータ依存で、北海道伊達市の ID となるか、福島県伊達市の ID となるか特定できない。DB で見つかった順だと思うがクエリまで特定できてない)
- 編集を開いたとき、項目の演算が走り、北海道に対して福島県伊達市を示す ID は異常値なのでデフォルト値にリセットされる
→ 値のリセットが起きる - 一覧は、項目の再計算をしないので、正しそうに見える
項目の連携に基づいてインポートする改善が望まれるところですが
-
データインポート時に、サイトの定義を確認する
-
定義に添って親テーブルを参照して補正する
ということをするので重いのみならず -
インポートデータに子データ(市町村)しかない場合に、テーブルのデータを参照すべきか
- 権限的に参照できるか
-
マッチしなかった場合、エラーなのか、クリアするのか、連携が取れないが子には存在するデータを入れておくのか
-
子側で 2 件以上あった場合にどうするか
といったあたりは、共通仕様として悩ましい問題になりそう。
現実的な解決
- 子テーブル側でタイトルの重複禁止をかけて、ユニークにする
この対応が取れればこれ一択。
ただ 792 市中重複している 4 市のために、市データを "北海道伊達市" "北海道札幌市" とするのが実用上、妥当とは認めにくいところがあります。
スクリプトでの解決方法
負荷を承知で、サーバスクリプトを ↓ こう書くと、適切に取り込めます。
条件は「作成前」および「更新前」に設定します。
try {
const childInput = model.ClassB;
const childId = Number(childInput);
// ID 値っぽいときだけ処理。→子テーブルにない異常値の場合、ID 置換されないため、特に手当しない
if (`${childId}` === childInput) {
const child = items.Get(childId)[0];
const parentOfChild = child.ClassA;
const parent = model.ClassA;
const childTitle = child.Title;
// 親子の対応が取れていないときのみ手当
if (parent !== parentOfChild) {
const childSite = 7031180;
const view = {
View: {
ColumnFilterHash: {
Title: childTitle,
ClassA: JSON.stringify([parent]),
},
},
};
const candidates = items.Get(childSite, JSON.stringify(view));
// 子テーブルにタイトルでマッチするものが存在する場合、その先頭の ID をとる。
if (candidates.Length > 0) {
const validatedChild = candidates[0];
model.ClassB = `${validatedChild.ResultId}`;
} else {
model.ClassB = childTitle;
}
}
}
} catch (e) {
context.Log(e.stack);
}
コードの流れとして
- CSV が読み込まれて
model
になった時点で ID 値に置換されている- 置換されていない場合、子に存在しない表示名なので無視
- ID から、データを取り、親子がマッチしているか照合
- マッチしているならヨシ
- 表示名で検索して、マッチするものがあればその先頭の ID を持ってくる
- なければ、表示値に戻す
デモ
- 親データ
- 子データ
- インポートデータ
"ID","タイトル","都道府県","市区町村"
"7119590","テスト1-","北海道","伊達市"
"0","テスト2","福島県","伊達市"
"0","テスト3","東京都","府中市"
"0","テスト4","広島県","府中市"
"0","テスト5","神奈川県","府中市"
- インポート
まとめ
- 項目連携は機能として難しい
- プリザンターは画面で入力することを基本に置いている
デモサイトですぐ使えるサイトパッケージがgistにあります。
Discussion