「rex0220 kSQL アクションプラグイン」の実践編です。
受注を登録・変更・削除したら、顧客別の売上も更新したい — ボタンを押さなくても、レコードの保存・ステータス変更・削除をきっかけに SQL を動かせます。関連レコード集計プラグインと同じ「変更があったら集計し直す」使い方を、SQL で書けるようにしたものです。
顧客コードのようなキーを変えたときも、変更前と変更後をまとめて 1 回で再集計できます。①〜⑤ は利用者がボタンで起動する使い方でしたが、この記事は通常のレコード操作だけで集計が追いかけてくる形です。
イベントで動かすときの決まり、キーを変えたときの後始末、レコードを消したときの扱い、そしてイベントに置く SQL の書き方をまとめます。
シリーズ: 概要 / ① 詳細画面 / ② 一覧 / ③ 集計 / ④ 運用 / ⑤ 事例 / ⑥ イベント(この記事) / ⑦ オーサリング / ⑧ 請求データ / ⑨ 締めの画面 / ⑩ どこに出すか / ⑪ 多言語 / ⑫ オーサリング改善
仕組みの概要
画面の操作をプラグインが受け取り、関係するキーを組み立ててから SQL を実行します。
- 控えは、詳細・編集画面を開いたときと、削除する直前に取ります。変更前のキーと、消したレコードのキーはここからしか拾えません
-
キーの配列は、起動の種類に応じてプラグインが組み立てます(下の表)。SQL 側は中身を気にせず
IN @keysと書けます
どのイベントで動かせるか
アクションの「起動」で選びます(複数選べます)。
| 起動 | タイミング | PC | モバイル |
|---|---|---|---|
| 保存後(追加) | レコードを追加して保存した後 | ○ | ○ |
| 保存後(編集) | レコードを編集して保存した後(一覧のインライン編集を含む) | ○ | ○ |
| ステータス変更 | 変更が確定した後(変更後の値で実行) | ○ | ○ |
| 削除したあと | レコードを削除して、削除が成功した後 | ○(詳細画面・一覧) | ○(詳細画面) |
設定画面では、アクションの「起動」のダイアログでイベントを選びます(複数可)。
動くのはブラウザーの画面の操作だけです。REST API・CSV 読み込み・ほかのプラグインの REST 書き込みでは動きません(そのかわり、アクションどうしが連鎖して無限ループになることもありません)。
イベントのアクションの決まり
- 種類は「レコード」だけです(アプリ単位のアクションはイベントに置けません)
- 確認なしに固定です(保存のたびに件数の確認は出せません)。そのぶん書き込み上限(1 以上)が必須です
- 入力項目は持てません(保存のたびに人に聞けないため)。絞り込みは実行条件(計算式)で書きます
- 保存後のイベントは、処理が終わるまで画面の遷移を待たせます。長い処理はボタン実行か kSQL Flow を検討してください
- SQL が失敗しても、保存済みのレコードは元に戻りません。失敗は遷移後の画面で知らせます(回復できるよう、同じアクションをボタンにも紐づけておくと手で流し直せます)
- ただし
@_status/@_prev_statusはステータス変更のときだけ入る予約パラメーターです。これらを使う SQL をボタンから押すと値が空になるので、回復用のボタンはイベント専用の値に頼らない形にしておきます
どれくらい待たされるか
保存のあと、SQL が終わるまで画面が切り替わりません。この記事の例(受注 22 件のアプリで、関係する 2 顧客を集計し直す形)をログアプリ付きで実測すると、API 6 回・約 1 秒でした。担当者別の明細アプリも一緒に直す形(③ 集計編)にすると、同じ操作で API 13 回・約 2 秒になります。
実行時間は読む件数と SQL の中身で決まります。キーで絞って関係する分だけ読む形にしておけば、集計元が増えても待ち時間は伸びにくくなります。逆に毎回全件を読む形は、イベントではなくボタンや kSQL Flow に回してください。
実行中の見せ方
保存から遷移までの間に動くので、実行中であることを必ず見せます。共通設定の「処理中の表示」で、ダイアログ(回る印+アクション名。操作を塞ぐ)か、画面上部のスピナーだけかを選べます。
ログ
イベントは保存のたびに起きるので、ログの残し方をアクションごとに「記録する/失敗だけ/記録しない」から選べます。既定は「失敗だけ」です(1 日 100 件の更新でも、年に数万件のログにはなりません)。
キーを変えたとき — 「キーの配列」で新旧をまとめて渡す
顧客別集計のようにキーの項目(顧客コードなど)で集計していると、キーを直したときに困ります。新しい顧客は直っても、前の顧客が古いまま残るからです。
そこで、パラメーターの源泉を「キーの配列」にします。式を 1 つ(例: 顧客コード)書いておくと、プラグインがその起動で関係するレコードすべてに式を当て、重複と空を落とした配列で渡します。
| 起動 | 配列の中身 |
|---|---|
| ボタン(詳細画面)・一覧の 1 件ずつ | [そのレコード] |
| 一覧の「まとめて 1 回」「グループごと」 | 選んだレコード全部 |
| 保存後(追加) | [新しい値] |
| 保存後(編集) |
[変更後, 変更前](同じなら 1 つに縮みます) |
| 削除したあと | [消したレコードの値] |
設定は、パラメーターの源泉を「キーの配列」にして式を 1 つ書くだけです。
イベントの種類で SQL を書き分けなくて済むのがこの形の狙いです。配列は DECLARE @keys = []; で受け、IN @keys で使います。
処理は「集める → 集計する → 書き込む」の 3 つに分けておきます。1 文にまとめても動きますが、分けたほうが読みやすく、設定画面の[実行計画]も 1 文ずつ対応します。
-- @ksql dialect: 1
DECLARE @keys = []; -- パラメーター(源泉「キーの配列」。式は 顧客コード)
-- 1. 集める。受注の 1 行を「件数 1」として並べ、今の集計を「0 の行」として足す
CREATE TEMP TABLE #raw AS
SELECT 顧客コード, 1 AS 件数, 金額 AS 売上
FROM LAPP_受注 WHERE 顧客コード IN @keys
UNION ALL
SELECT 顧客コード, 0 AS 件数, 0 AS 売上
FROM LAPP_集計 WHERE 顧客コード IN @keys;
-- 2. 集計する。顧客コードごとに足すだけ(kintone には行きません)
CREATE TEMP TABLE #agg AS
SELECT 顧客コード, SUM(件数) AS 件数, SUM(売上) AS 売上
FROM #raw GROUP BY 顧客コード;
-- 3. 書き込む。受注が 0 件になったキーは「0 の行」だけが残るので 0 になる
UPSERT INTO LAPP_集計 (顧客コード, 件数, 売上, 集計日時)
SELECT 顧客コード, 件数, 売上, @NOW() AS 集計日時
FROM #agg
ON DUPLICATE (顧客コード);
#raw には受注の明細がそのまま入ります。kSQL の一時テーブルには行数の上限があり、プラグインの共通設定で 1 表 10,000 行・全表の合計 20,000 行が既定です(超えるとエラーになります。黙って切り捨てることはありません)。数えるのは受注の取得件数と、UNION ALL で足す集計側の行数の合計です。1 回で何千件も触るアクションでは、1 の受注側を COUNT(*) AS 件数, SUM(金額) AS 売上 … GROUP BY 顧客コード にして先に集計してください。
顧客コードを変えて保存すると、1 回の実行で新旧の両方が直ります。受注 1 件(60,000 円)の顧客コードを C003 から C900 に変えてみます。
変える前の顧客別集計です。C003 は 6 件 445,000 円です。
受注の顧客コードを C900 に変えて保存しただけの状態です。C900 が 1 件 60,000 円で作られ、C003 は 5 件 385,000 円に減りました(集計日時はどちらも同じ)。関係しない顧客の集計日時は変わっていません — 関係するキーだけを触っているからです。
保存したときの流れはこうなります。
変更前の値は、画面を開いたときに取っておいた控えから拾います。控えを取るのは次のときで、そのタブを閉じるまで残ります。
- 詳細画面を開いたとき / 編集画面を開いたとき(編集の URL を直接開いた場合も含みます)
- 一覧のインライン編集を始めたとき(PC)
- モバイルの詳細画面・編集画面を開いたとき
控えに期限は付けていません。画面を開いてから保存するまで何分かかっても拾えます。通常の編集画面では、別の人が先にレコードを更新していると重複更新のチェックで保存できないので、ふだんの編集操作では控えと保存の中身が食い違いません。
そのタブで画面を開かずに保存した場合(まれですが)は、変更後のキーだけで動きます。集まったキーの分は正しく直るので実行は止めませんが、前のキーは古いまま残ります。気づいたときに直せるよう、全件を集計し直すアクションをボタンとして用意しておくことをおすすめします(後述)。
なお、式の結果がすべて空のときは配列が空になるので、実行しません(書くものが無いためです)。
取りこぼしが起きるのはどんなときか
イベントは便利ですが、集計が必ず同期することを保証する仕組みではありません。次の場合は手当てが要ります。
| ケース | 起きること |
|---|---|
| 変更前の控えが無い(そのタブで画面を開かずに保存した) | 古いキーの集計が直らない |
| ステータス変更の後にタブを閉じた | その実行が行われない |
| REST API・CSV で更新・削除した | イベントが起きない |
| 同じキーを同時に集計した | 古い集計結果があとから書かれることがある |
| SQL の途中で失敗した | 集計元と集計先が食い違う(複数の文はトランザクションではありません) |
どれも「全件を集計し直すアクション」(後述)で直せます。再集計の最中に更新が重なると同じ競合が起きるので、ほかの更新が走っていないときに押してください。業務の性格上どうしても外せないなら、kSQL Flow / kSQL ジョブ実行で定期的に流す形も検討してください。
「0 件になった集計先」の直し方
GROUP BY で集計すると、元が 0 件のキーは1 行も返りません(書き込む行が無いので、集計先は触られません)。最後の 1 件を動かした顧客の集計は、放っておくと古い値のまま残ります。上の SQL が「今の集計を 0 の行として UNION ALL で足す」形になっているのはこのためです(詳しくは ③ 集計編 と ⑤ 事例編)。
- 先に
UPDATE … SET 件数 = 0で埋める形より安全です。0 にしてから集計する形は、0 にしたあとで失敗すると 0 のまま残ります - 行ごと消したいときは、集計のあとで
DELETE FROM LAPP_集計 WHERE 顧客コード IN @keys AND 件数 = 0;を足します。先に消すと、失敗したときに行ごと無くなります
レコードを消したとき
起動に「削除したあと」を選ぶと、レコードを消した後にアクションが動きます。
- 消す直前に控えを取り、削除が成功してからその控えのキーで実行します
- レコードはもう無いので、除外の指定は要りません。上の SQL がそのまま使えます
- 拾えるのは画面から消したときだけです(REST API・CSV の削除では動きません)
実際に、受注を 1 件登録してから削除すると、その顧客の集計は 0 件 / 0 円になりました。
削除より「削除フラグ」をおすすめします
運用としては、レコードを消さずに「削除フラグ」の項目を立てるほうが安全です。履歴が残り、誤操作も戻せます。
SQL は上の形のまま、受注を読む側の WHERE に条件を足すだけです。
-- 1「集める」の受注側だけ直します(2 と 3 はそのまま)
FROM LAPP_受注 WHERE 顧客コード IN @keys AND 削除フラグ <> '削除'
UNION ALL の「0 の行」はそのまま残します。最後の 1 件にフラグを立てると集計の結果が 0 行になるので、0 の行が無いと集計先が古い値のまま残ります(前の節と同じ理由です)。
- 条件の書き方は項目の型で変わります(kintone のクエリの決まり)。文字列 1 行なら
AND 削除フラグ <> '削除'、ドロップダウンならAND 削除フラグ NOT IN ('削除')です。ドロップダウンに<>(!=)は使えません - どちらの書き方でも、まだフラグを付けていない空欄のレコードは集計に入ります(22 件のうち 1 件だけ値を入れた項目で試すと、どちらの条件でも残りの 21 件が対象になりました)
- フラグを立てる操作は「編集」なので、保存後(編集)のイベントで拾えます。ただし REST API や CSV 読み込みでフラグを更新した場合は、ほかの更新と同じくイベントは動きません(全件を集計し直すアクションで直します)
ステータス変更は「遷移の後」で動きます
ステータス変更のイベントは、押した時点ではまだ遷移が確定していません(レコードの値も変更前です)。そこでプラグインは、変更の記録だけを残し、遷移後の詳細画面で、現在のステータスが記録どおりであることを確かめてから実行します。
承認のステータスで集計対象を切り替える、といった使い方ができます。
| 変更 | 集計 |
|---|---|
| 未承認 → 承認済み | 集計に入る |
| 承認済み → 差戻し | 集計から外れる |
受注を読む側の WHERE に AND 処理状況 = '承認済み' のような条件を足しておけば、変更後の状態でまるごと集計し直すので、差分を足し引きする必要はありません。
- SQL からは
@_status(変更後)と@_prev_status(変更前)が使えます - 遷移が確定しなかったときは実行しません
- 遷移の直後にタブを閉じると実行されません(同じタブで 10 分以内にその詳細画面を開けば実行されます)
イベントに置く SQL の書き方
イベントは保存のたびに起きます。差分を足し引きする書き方にすると、保存のたびに値がずれていきます。
-
関係する範囲を毎回まるごと計算し直す形にします(
UPSERTや全置換)。関連レコード集計が、1 件の変更で関係するレコード全体を集計し直すのと同じ考え方です -
INSERTで足していくだけの SQL は置かないでください(保存のたびに増えます) - 同じ項目を足し引きする代入(
売上 = 売上 + 1など)も避けます
この 3 つは、設定を保存するときに注意として知らせます(保存は止めません)。
動かないところと、取りこぼしの直し方
| 操作 | イベント |
|---|---|
| PC・モバイルのレコード追加・編集の保存 | 動く |
| PC の一覧のインライン編集 | 動く |
| ステータス変更 | 動く(遷移の後) |
| PC の詳細画面・一覧からの削除、モバイルの詳細画面からの削除 | 動く |
| REST API による更新・削除(他システム・他プラグインを含む) | 動かない |
| CSV(ファイル)読み込みによる更新 | 動かない |
漏れなく反映したい運用(外部システム連携があるアプリなど)は kSQL Flow / kSQL ジョブ実行を検討してください。
取りこぼしに気づいたときのために、全件を集計し直すアクションをボタンで用意しておくと安心です。集計元が数百件までのアプリなら、次の形で全部を作り直せます。
-- @ksql dialect: 1
-- 1. 集める。全件なので、受注側は先に集計しておきます(一時テーブルは 10,000 行まで)
CREATE TEMP TABLE #raw AS
SELECT 顧客コード, COUNT(*) AS 件数, SUM(金額) AS 売上
FROM LAPP_受注 GROUP BY 顧客コード
UNION ALL
SELECT 顧客コード, 0 AS 件数, 0 AS 売上 FROM LAPP_集計;
-- 2. 集計する
CREATE TEMP TABLE #agg AS
SELECT 顧客コード, SUM(件数) AS 件数, SUM(売上) AS 売上
FROM #raw GROUP BY 顧客コード;
-- 3. 書き込む
UPSERT INTO LAPP_集計 (顧客コード, 件数, 売上, 集計日時)
SELECT 顧客コード, 件数, 売上, @NOW() AS 集計日時
FROM #agg
ON DUPLICATE (顧客コード);
イベントで動かす処理と同じ組み立てで、キーで絞らず、1 の受注側を先に集計しただけの形です。
二重実行とログアプリ
イベントはロックを取りません(保存のたびに起きるので、ロックで止めると保存が滞るためです)。同じキーに対する処理が同時に動くことがあります。
まるごと計算し直す形で書いていれば、同じ処理が何度流れても二重に足されることはありません。ただし、同時に動いたときは次のような順序がありえます。
- A さんの保存で受注を読む
- B さんの保存で受注を読み、集計を書く
- A さんの処理が遅れて、古い集計結果を書く
結果として、集計先に古い値が残ることがあります(そのキーが次に集計されるまで直りません)。厳密な整合が要るときは、同じキーを同時に触らない運用にするか、定期的に全件を集計し直す手当てを入れてください。
ログアプリを指定していれば、イベントの実行も記録できます(既定は「失敗だけ」)。ログの見方と二重実行の防止は ④ 運用編 にまとめています。
関連記事
- rex0220 kSQL アクションプラグイン
- ① 詳細画面編(ボタンとイベント)
- ② 一覧編(選択したレコードをまとめて処理する)
- ③ 集計編(顧客別集計とテーブルへの書き戻し)
- ④ 運用編(ログアプリ・二重実行の防止・権限)
- ⑤ 事例編(10 万件を集計して Dashboard Pro で見る)
- ⑥ イベント編(保存・ステータス変更・削除で自動実行する)(この記事)
- ⑦ オーサリング編(設定を AI に作ってもらう)
- ⑧ 請求データ編(全体再計算で整合性を保つ)
- ⑨ 締めの画面編(押し忘れに気づくダッシュボード)
- ⑩ 画面のどこに出すか(ボタン・メニュー・パネルの使い分け)
- ⑪ 日本語・英語・中国語で使う(共通の文と多言語)
- ⑫ オーサリング改善編(AI に使わせて見つけた穴)
- 事例集(やりたいことから探す)



