レコードホールドとバックヤードテーブル


1レコードの複数のフィールドを編集した後でも・・・簡単に元に戻せる!

本当に削除していいのかどうか迷うレコード・・・取りあえずバックヤードに放り込む!

概要

人間は必ずミスをする生き物です.レコードを変更したり削除したりするときにはそれなりに緊張感が伴います.

FileMakerでは,あるフィールドにカーソルを置いてフィールド内容を編集している間は,Command(Ctrl)+Zで取り消しが効きますが,別のフィールドに移動してしまうと(レコードを確定してしまうと,基本的には)取り消しが効きません.
たとえば,1レコードの中の,複数のフィールド内容を変更した後に,「しまった,このレコードじゃなかった.直すのは別のレコードだった.しかし,フィールドの元の値を覚えていない・・・」となったことはないでしょうか? バックアップファイルを取ってあっても,1ファイル丸ごとバックアップから切り戻し(フェイルバック)するのはなかなか面倒だったりもします.

そんな時に役立つTipsを紹介します.ちょっと似たようなテクニックは、FMGo.jpで、
https://fmgo.jp/detail.php?recid=701
株式会社イエスウィキャンさんが
「入力データの復帰ができる編集機能ファイル【Commit Control】」として公開されていますが、それとは若干、アプローチが異なります。

「レコードホールド」も「バックヤードテーブル」も,自分が編み出した造語ですが,何のことかまずはファイルの動きを見せて、その後、種明かしと解説をします。

(作例のデータ内容はあくまでサンプルですが,家電製品の在庫表のようなものを考えてみました)

レコードホールド

レコードホールドとは,現在のレコードの値(複数のフィールド内容)を,現在の値のままに保管しておくことです.
「今から重要なレコードをいじるんだけど,失敗したくない」という時にかける保険のようなものです.
テレビゲームのポーカーをやったことのある方なら想像がつくかと思いますが,配られた手札のうち,手元に残しておきたいカードを「ホールドする」と言うと,「チェンジしない・残しておく」という意味になります.それと同じように,複数のフィールド内容をホールドすることができます.

レコードをHoldしたいときには,値を編集する前に必ず,Holdボタンを押します.(レコードがHoldされ=ボタンの表示が薄くなります。もう一度押すとHold解除されます)
複数のフィールドを編集してみます.

ここでは例として,テレビの価格を80000→70000に,数量を10→5に変更してみます.

Revive(復活)ボタンを押すと,変更したはずの複数のフィールドの値が,Holdしたときの状態に戻ります.

種明かし

Holdボタンを押した時

変更する前の状態の各フィールドの値を,JSONに格納して「Jsonaized」フィールドに入れています.
いわば,「レコード内の1フィールドに他のフィールドをJSON化してバックアップした」状態です.

(スクリプト)

Holdボタンのスクリプト

# 
# フラグが立っていなければ立てる、立っていれば消す
変数を設定 [ $flg ; 値: Product::flg_Hold ] 
# 
If [ $flg ] 
    フィールド設定 [ Product::flg_Hold ; "" ] 
    現在のスクリプト終了 [ テキスト結果:    ] 
Else
    # 
    # JSONSetElement_現在のレコードを$$JSONに入れてフィールドに保存
    # 
    # 各フィールドの内容をJSONSetElementsで追加していく
    変数を設定 [ $JSONItem ; 値: JSONSetElement ( $JSONItem ; "ItemName" ; Product::ItemName ; JSONString ) ] 
    変数を設定 [ $JSONItem ; 値: JSONSetElement ( $JSONItem ; "Price" ; Product::Price ; JSONNumber ) ] 
    変数を設定 [ $JSONItem ; 値: JSONSetElement ( $JSONItem ; "Qty" ; Product::Qty ; JSONNumber ) ] 
    変数を設定 [ $JSONItem ; 値: JSONSetElement ( $JSONItem ; "Comment" ; Product::Comment ; JSONString ) ] 
    # 
    # 配列に追加する(型はオブジェクト)
    # 
    変数を設定 [ $MakeJSON ; 値: JSONSetElement ( $MakeJSON ; "Product.Item[" & 0 & "]" ; $JSONItem ; JSONObject ) ] 
    # 
    # JSONSetElementで作成すると、改行も空白もない見にくい形なので見やすい形に直す
    変数を設定 [ $MakeJSON ; 値: JSONFormatElements ( $MakeJSON ) ] 
    変数を設定 [ $$JSON ; 値: $MakeJSON ] 
    # 
    フィールド設定 [ Product::Jsonized ; $$JSON ] 
    フィールド設定 [ Product::flg_Hold ; 1 ] 
    # 
    レコード/検索条件確定 [ ダイアログあり: オフ ] 
End If

Reviveボタンを押した時

JSONからそれぞれの値を取り出し,もとのフィールドに入れるだけです.
(スクリプト)

Reviveボタンのスクリプト

# 
# JSONから各フィールドに入れる
# 
変数を設定 [ $$JSON ; 値: Product::Jsonized ] 
フィールド設定 [ Product::ItemName ; JSONGetElement ( $$JSON ; "Product.Item[" & 0 & "].ItemName" ) ] 
フィールド設定 [ Product::Price ; JSONGetElement ( $$JSON ; "Product.Item[" & 0 & "].Price" ) ] 
フィールド設定 [ Product::Qty ; JSONGetElement ( $$JSON ; "Product.Item[" & 0 & "].Qty" ) ] 
フィールド設定 [ Product::Comment ; JSONGetElement ( $$JSON ; "Product.Item[" & 0 & "].Comment" ) ] 
フィールド設定 [ Product::flg_Hold ; "" ] 
レコード/検索条件確定 [ ダイアログあり: オフ ] 


なぜこんな手法を採用したのか?

値を保管しておきたい,というだけなら,わざわざJSONなど使わなくても,レコードごと複製すればいいのでは? と考える方もいるでしょう.しかし,その方法だと,Holdしたいレコードが多数ある場合にはレコード数も増えてしまいますし,また,元のレコードと,複製したレコードとの関連がわかるようにフラグを立てたり,リレーションを組んだりして,データベースの構造にも影響を与えなくてはなりません.
集計フィールドがある場合などは,複製したレコードは「重複レコード」になるので,集計結果から重複分を差し引く処理が発生したりもします.
しかし,この手法なら,リレーションも使わず,レコード数を増やさず,集計に影響を与えず,わずか1フィールドで,同様のことを実現できます.
あるいは別の使い方として,Holdボタンを「読んだ・確認した」ボタンとして使うと,Holdボタンが濃いレコードは[未読=バックアップしていない」,Holdボタンが薄いレコードは「既読=読んで,内容にOKした・ハンコを押した=バックアップ済み」という意味で使用することもできるでしょう.

  • メリット

    • 総レコード数に影響を与えない.
    • 集計フィールドなどに気を遣わないで済む
  • デメリット

    • 1フィールドずつJSON化するのでフィールド数が多いと実装が大変.
    • FileMakerとJSONでは使える型が異なるので,FileMakerにはあって,JSONにはない「日付」「時刻」「計算フィールド」などの取り扱いが面倒.

もちろん,スクリプトを使えば,「デフォルトでは全てのレコードをHold」「スクリプトトリガのOnObjectModifyでHold」「ある条件にマッチしたものだけHold」などの実装も簡単にできるでしょう.

いろいろな使い道があるかもしれませんが,主眼は「複数のフィールドの値の変更をCommand(Ctrl)+Zのように簡単に直す」ためのテクニックです.

ちなみに、冒頭で紹介した、株式会社イエスウィキャンの「入力データの復帰ができる編集機能ファイル【Commit Control】」と異なる点は、

  • イエスウィキャンの例では、基本、全レコードHoldしているのが前提で、「編集開始」をフラグとして、レコードを元に戻すためのトリガとしているが、私の例では、デフォルトではHoldしていないことが前提で、どのレコードがHoldされているかを明示するのが主眼。また、いちいち、「編集開始」を宣言する必要がない(=頻繁にレコード編集される可能性があるソリューション向け)。

  • イエスウィキャンの例では、元に戻したいとき、1レコード全体をレコード復帰するのに対し、私の例では、戻したいフィールドを選んでJSON化しているので、任意のフィールドの復帰が可能。

といったところです。どちらを使うかは状況次第で変わるでしょう。

もっとデータの保管を堅牢にしたい、という方は、FileMaker18からの「データファイル」で、特定のファイルパスにJSONを書き出しておき、必要に応じてそこから読み込む、という使い方もあるでしょう。


バックヤードテーブル

レコードの削除の時も,「しまった,消すのはこのレコードじゃなかった」と青ざめたことはありませんか?(私はあります)
このレコード,消していいのだろうか?,いずれ使うときが来るのだろうか? 迷っているうちに,どんどんゴミのようなデータが貯まっているテーブル,ありませんか?(私はあります)

レコード削除に伴う切り戻し(フェイルバック)を提供するのが,バックヤードテーブル(自分なりの命名)です.「このレコード,いらないな.でもレコードを完全に削除するのは恐いな」というときに使います.

まずは動きだけを見てみましょう.

(作例)
この例では,商品が店頭に並んでいる,店頭商品の一覧を表示しています.

「このレコード,取りあえずこのテーブルから取り除きたい.でも削除はしたくない」と思うレコードの「Backyard」ボタンを押します.

カードウインドウが表示され,現在のテーブルからレコードは削除され,バックヤードテーブルに移行したことがわかります.ついでにバックヤードに何と何があるか、バックヤードの在庫分の商品価格の合計(棚卸しなどに必要)も表示されます。

(カードウインドウを閉じます)

バックヤードに移すのではなく、単にバックヤードにある商品を見たいときには,目玉アイコンの「Backyard」ボタンを押します.(カードウインドウが開きます)

バックヤードから店頭(この場合は「Product」テーブル)に戻したいときには,「Front」ボタンを押します.

Productテーブルにレコードが戻りました。(あえてソートはしていないので,レコードは必ずProductテーブルの末尾に追加されます)

※レコードを完全に削除したい場合は,どちらのテーブルでも「ゴミ箱」ボタンを押します.(この場合はダイアログもなしでいきなり削除されるようにしてあります)

種明かし

バックヤードテーブルは,現在のテーブルと全く同じフィールド定義を持った,バックアップ用のテーブルです.
現在のレコードだけを対象レコードとしておいて,バックヤードテーブルに1レコードのインポートをして,その後,元のテーブルのレコードを削除しているだけです.

(スクリプト)

現在のレコードをバックヤードに移動するスクリプト
レコードを対象外に
対象外のみを表示
# 
# 現在のレコードだけを対象にして,レイアウトを切り替える.こうすることで1レコードだけのインポートができる
変数を設定 [ $FilePath ; 値: Get (ファイルパス) ] 
# 
レイアウト切り替え [ 「Backyard」 (Backyard) ; アニメーション: なし ]
レコードのインポート [ ダイアログあり: オフ ; テーブル: Backyard ; 「$FilePath」 ; 追加; UTF-8 ] 
# 
レイアウト切り替え [ 「Product」 (Product) ; アニメーション: なし ]
対象レコード削除 [ ダイアログあり: オフ ]
全レコードを表示
# 
# 元のウインドウの幅と高さを記録
変数を設定 [ $WindowW ; 値: Get(ウインドウ内容幅) ] 
変数を設定 [ $WindowH ; 値: Get(ウインドウ内容高さ) ] 
新規ウインドウ [ スタイル: カード ; 名前: "Backyard" ; 使用するレイアウト: 「Backyard」 (Backyard) ; 高さ: $WindowH *.8 ; 横幅: $WindowW *.8 ; 上: Get(ウインドウ上位置)+ $WindowH *.1 ; 左: Get(ウインドウ左位置)+ $WindowW *.1 ] 

バックヤードからProductテーブルへ戻すスクリプト
レコードを対象外に
対象外のみを表示
# 
# 現在のレコードだけを対象にして,レイアウトを切り替える.こうすることで1レコードだけのインポートができる
変数を設定 [ $FilePath ; 値: Get (ファイルパス) ] 
# 
# BackyardからProductへインポート
レイアウト切り替え [ 「Product」 (Product) ; アニメーション: なし ]
レコードのインポート [ ダイアログあり: オフ ; テーブル: Product ; 「$FilePath」 ; 追加; UTF-8 ] 
# 
レイアウト切り替え [ 「Backyard」 (Backyard) ; アニメーション: なし ]
対象レコード削除 [ ダイアログあり: オフ ]
# 
ウインドウを閉じる [ 名前: "Backyard" ; 現在のファイル ] 

作例のようなものだと,店頭在庫の商品の金額の合計と,バックヤードの商品の金額の合計を別々に出すことができるので,何か使い道があるかもしれません.

  • メリット

    • 取りあえずいらないものはバックヤードに放り込んでおけるので,現在のテーブル(作例では「Product」はきれいなまま保てる
  • デメリット

    • データとしては冗長になるので,ファイルが肥大化しがちです.
    • バックヤードテーブルがゴミ溜めみたいになるので,やはり定期的な整理と削除は必要です.

解説

膨大なレコード数を持つデータベースでも,その中で頻繁にアクセスされ,更新されるレコードは実は数パーセントしかない,ということはよくあります.逆にいえば,「貯めてあるだけの,使われないほとんどのレコード」は検索速度を落とす原因となっています.作例での店頭(Product)テーブルは常に軽く,早く保ちたいときにおすすめです.まず、店頭テーブルだけを検索し、「あれ、ないな」と思ったらバックヤードテーブルを検索しに行くスクリプトを書いておくと便利です。

あくまで例として「店頭」と「バックヤード」を挙げましたが、これらは、「普段使うレコード」「あまり使わないレコード」として捉えるとさまざまな場面で活用できるかと思います。

通常はこの例の「店頭」と「バックヤード」は1つのテーブルに保存され,何らかのフラグやフィールドによって管理される類のものですが,使わないレコードをあえて別テーブルに移行してしまうことで,検索速度を高めることができます.
(運用の仕方にもよりますが,レコードの最終更新日などから「あまり使われていないレコード」を分析し,スクリプトで定期的にバックヤードテーブルに送ってしまうこともできるでしょう。)

(この作例ではそのようにしてはいませんが)また別の運用によっては,まずバックヤードに入荷し,その後,品出し(店頭に陳列)というフローもあるかもしれません.

サンプルファイルのダウンロード

レコードホールド_バックヤードテーブル公開用.fmp12

あとがき

今回の作例では,レコードホールドとバックヤードテーブルの両方を実装していますが,この2つはセットというわけではありません.片方だけでも、その時の条件に応じて,適宜使い分けてみてください.

レコードの編集の際にも,削除の際にも,ミスはつきものです.気をつけて,安全なFileMakerライフを!