🍊

Pleasanter でリンクした項目をインポートするときの問題への対応

2022/12/03に公開

2022 個人アドベントカレンダー の記事です。

課題感

↓ をコードで無理矢理対応する

制限事項
「項目連携」を設定している「テーブル」で「インポート」を行うと親子関係が正しく登録できない場合があります。「インポート」では表示名に重複がある場合、「項目連携」の設定に関わらず最初に見つかったデータを紐づけます。「項目連携」の子「項目」の「表示名」に重複がある場合、親「項目」の値と関連の無い子「項目」の値がセットされる可能性があります。親「項目」の値と関連の無い子「項目」の値がセットされた場合、「エディタ」を開くと子「項目」の値がクリアされ未入力の状態となります。

ちょっとなに言ってるか分からない

どういう機能のどういう問題?

基本仕様

  1. 複数のテーブルに親子関係をつけることで、プルダウン(分類型項目)で "大分類" > "小分類" のような段階的な絞り込み(プルダウンの連動)を実現することができる。
    • これが「項目連携」の機能
    • 項目連携のプルダウンでは、連携した外部テーブル(マスタテーブル)のタイトルの値が入る
  2. 項目連携したとき、内部データには連携先のテーブルのレコード ID が保持されている
  3. 項目連携の表示値 →ID の置換は、CSV の取り込みのときにも働くので、CSV に表示値が入っていると ID 値に置換されて保持される

ここまでがベースになる仕様です。

問題が起こる状況

マニュアルの例の都道府県と市町村で次のようなデータを仮定します。

  • 都道府県マスタ
    • 北海道
    • 福島県
    • 東京都
    • 広島県
  • 市町村マスタ
    • 伊達市 ← 北海道に紐付け
    • 伊達市 ← 福島県に紐付け
    • 府中市 ← 東京都に紐付け
    • 府中市 ← 広島県に紐付け

このマスタを参照する住所録的なデータで ↓ のようなデータを取り込んだとき、次のような問題が発生します。

北海道,伊達市
福島県,伊達市
東京都,府中市
広島県,府中市
  1. 一覧表では正しく取り込めて見える
  2. 北海道伊達市をインポートしたデータを編集画面で見ると、市町村データが消える
    開いた時点で消えてます
  3. (↑ 消えたことに気付かず)更新すると、データが消えます。
    (一覧からも消えます)
  4. 同じことが広島県府中市でも発生します。
    でも、福島県伊達市と東京都府中市では再現しない。
    県に対して市名がユニークであるこの余の 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 を持ってくる
    • なければ、表示値に戻す

デモ

  • 親データ

pref.png

  • 子データ

city.png

  • インポートデータ
"ID","タイトル","都道府県","市区町村"
"7119590","テスト1-","北海道","伊達市"
"0","テスト2","福島県","伊達市"
"0","テスト3","東京都","府中市"
"0","テスト4","広島県","府中市"
"0","テスト5","神奈川県","府中市"
  • インポート

importaddress.gif

まとめ

  • 項目連携は機能として難しい
  • プリザンターは画面で入力することを基本に置いている

デモサイトですぐ使えるサイトパッケージがgistにあります。

GitHubで編集を提案

Discussion