0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

rex0220 kSQL Dashboard Pro - SFA パックに SQL でダッシュボードを作る

0
Last updated at Posted at 2026-07-31

kintone アプリストアの SFA(営業支援)パックは、顧客管理・案件管理・活動履歴が最初から入っている便利なパックです。ただ、標準の一覧画面だけだと「今いくら受注していて、どのフェーズに何件たまっているのか」がひと目では分かりません。

そこで kSQL Dashboard Pro(有償プラグイン)を使い、SFA パックの一覧画面の上にダッシュボードを作ります。

プラグインの機能全般(表示タイプ・設定画面・テーマ・タブなど)は 製品紹介記事 を参照してください。本記事は SFA パックでの実践に絞ります。

この記事では、SQL・実際のペイン設定・表示結果を 1 セットにして 9 個ぶん並べました。SQL を書いたことがない方でも、コピペ → 設定画面で 2〜3 か所選ぶ、の手順で同じ画面が作れます。

完成したダッシュボード(案件管理アプリの一覧画面)


前提(この記事で使うバージョン・アプリ)

バージョンの前提はここだけです。以降の本文では省略します。

項目 本記事の前提
プラグイン kSQL Dashboard Pro Ver.1
SQL エンジン kintone-sql-tools 3.36.0
kintone 公式対応ブラウザの最新版(PC)。モバイルは対象外
対象アプリ アプリストア SFA(営業支援)パック
 顧客管理 APP4148(215 件)
 案件管理 APP4149(20 件)… ダッシュボードを置くアプリ
 活動履歴 APP4147(18 件)… 応用編で使用
 担当者管理 APP4150(本記事では未使用)

アプリ番号は環境ごとに変わります。 APP4149 などの数字は、ご自身の環境の番号(アプリの URL の /k/4149/ の部分)に読み替えてください。SQL を手で入力するときはこの 4 桁を書き換えるだけで動きます。
設定 JSON の取り込みでは書き換え不要です。 取り込み時の確認ダイアログに SQL 中のアプリ番号が一覧で出るので、変換先の番号を入力すると SQL を自動で置き換えて取り込めます(後述)。

SFA パックのサンプルデータは受注予定日が 2025 年で固定されています。そのため「今月の受注予定」のような相対日付のペインは、そのままだと 0 件になります。

本記事の 9 ペインは日付に依存しない集計だけで作ってあるので、この影響を受けません。掲載している数字は、あなたの環境でもそのまま再現できます。

相対日付を試したい場合は、日付を今日から見た位置へ振り直すスクリプトを用意しました。レコードを増やさず、既にある日付をずらすだけなので、フェーズ別の合計などは 1 円も変わりません

node shift-sample-dates.mjs              # 下見(何も書き換えない)
node shift-sample-dates.mjs --apply      # 実際に書き換える

shift-sample-dates.mjs(全文は記事末尾の付録)— Node 18 以上で動きます(依存パッケージなし)。


まず結論:5 分で同じ画面を作る

手を動かす前に完成形を見たい方は、設定 JSON を取り込むだけで再現できます。

先に ダッシュボード専用の一覧(カスタマイズ形式)を 1 枚作っておいてください(作り方は次の節)。ここでは 【全社】ダッシュボード という名前で進めます。

  1. 案件管理アプリ → アプリの設定プラグイン → kSQL Dashboard Pro の 設定
  2. ヘッダーの ツールインポート から sfa-案件管理-サマリー.json を選択
    (設定 JSON は記事末尾の付録からコピーして保存します)
    … 確認ダイアログの「アプリ番号の変換」に、APP4149 → ご自身の案件管理・APP4148 → 顧客管理 の番号を入力してインポート
    共通に入る KPI 1 段。すべての一覧の上に出ます
  3. ツールビューへ取り込む から sfa-案件管理-dashboard.json(付録)を選択
    … 確認ダイアログで取り込み先【全社】ダッシュボード を選び、同様にアプリ番号を変換して取り込む。この一覧だけの 9 ペイン
  4. 保存

なぜ 2 つに分かれているのか。 手順 2 の インポートは設定全体を置き換え、入れたものはすべての一覧に出ます
標準の一覧はレコードが主役なので、そこに 9 ペイン(3 段)を出すとレコードが画面の外へ押し出されます
そのため共通は 1 段のサマリーだけにし、全面のダッシュボードは専用の一覧に個別で置きます。

手順 3 の 「ビューへ取り込む」は、ダイアログで選んだ一覧だけを書き換えます。ファイルに複数のダッシュボードが入っていれば、どれを入れるかも選べます。取り込んだ後もダイアログは開いたままなので、同じファイルから続けて別の一覧へも入れられます。応用編の sfa-案件管理-分析.json(付録)も同じ方法で 【全社】分析 に入れます。

タブ版もあります。 同じ中身をタブで分けた設定も置いてあります。手順を変える必要はなく、
読み込むファイルを差し替えるだけです。

ファイル 中身 入れ方
sfa-案件管理-サマリー-タブ.json 共通。1 段のまま 2 タブ(受注 / ランキング) インポート
sfa-案件管理-タブ.json 専用一覧。9 ペインを1 段 × 4 タブ(サマリー / グラフ / 集計 / 明細) ビューへ取り込む
sfa-案件管理-グラフ8種.json グラフ 8 種すべてを 2 タブで。値ラベル・目標線・軸の単位などの実例つき ビューへ取り込む

ファイルはいずれも記事末尾の付録にあります。

いずれも表示中のタブだけを集計するので、ペインが増えても開く速さは変わりません(後述)。

既にペインを作ってある場合は、手順 2 の前に ツール → エクスポート で控えを取ってください。

以降は、この 9 個のペインを 1 つずつ「なぜその SQL なのか」「どこを設定したのか」に分けて説明します。

ダッシュボード専用の一覧を作る(おすすめ)

案件管理の一覧に【全社】ダッシュボード・【全社】分析を追加した状態

ダッシュボードは一覧画面の上部に出ます。標準の一覧に置くと、下にレコードの表が続きます。
それはそれで便利ですが、ダッシュボードだけを大きく見たいなら専用の一覧を作ります。

一覧の設定 — 表示形式を「カスタマイズ」にする(HTML は空のままでよい)

  1. アプリの設定 → 一覧 → 「+」で新規作成
  2. 形式を 「カスタマイズ」 にする(HTML は空のままでよい)
  3. 名前を付けて保存 → アプリを更新

カスタマイズ形式にはレコードの表が無いため、画面いっぱいにダッシュボードが出ます

本記事では 2 枚作りました。名前を 【全社】 で始めているのは、SFA パック標準の 【個人】… と対にするためです。
全社の数字であって、いま見ている一覧の集計ではないことが、開く前に名前で分かります。

一覧名 中身 入れ方
【全社】ダッシュボード この記事の 9 ペイン(この一覧だけの個別ペイン) …-dashboard.jsonビューへ取り込む
【全社】分析 応用編のランキングなど(この一覧だけの個別ペイン) …-分析.jsonビューへ取り込む

どちらもその一覧だけに入ります。共通(すべての一覧に出るサマリー 1 段)とは別物です。

どこに何を置くか(重要)

標準の一覧(全案件) — 共通のサマリー 1 段の下にレコードの表が続く

ダッシュボードは一覧画面のヘッダー部分に出ます。つまりレコードの表を下へ押し下げます
どれだけ押し下げてよいかは、その一覧でレコードと数字のどちらが主役かで決まります。

置き場 主役 置くもの 高さの目安
標準の一覧 レコード KPI カードを横 1 列。詳細はフル画面ボタンへ 1 段まで
カスタマイズ形式の一覧 ダッシュボード 何段でも。グラフ・表を並べる 制限なし
作業用の絞り込み一覧 レコード 出さない

高さの目安を実際の数字にすると、こうなります。ペインの高さ 1 行 = 40px、間隔 10px、これに表示サイズ(既定 110%)が掛かります

ペインの高さ 110%(既定) 150%(特大)
2 行 99px 135px
3 行 154px 210px
4 行(追加したときの既定) 209px 285px
3 段(この記事の 9 ペイン) 671px 915px

ノート PC(縦 768px)で kintone のヘッダーとツールバーを引くと、見えるのは 600px 前後
表のヘッダーと数行を残したいなら、標準の一覧では 1 段(2〜3 行)が上限です。3 段はレコードが完全に画面の外へ出ます。

だから 共通ダッシュボードはサマリー 1 段にします。共通は個別設定を持たないすべての一覧に出るので、いちばん厳しい条件(標準の一覧)に合わせておくのが安全です。

1 段では足りないとき — タブで「深さ」に変える

高さの上限は動かせません。ですがタブを使えば、高さを増やさずに中身を増やせます

段数 見られる数字 初回に走る SQL
サマリー 1 段 1 段(154px) 3 ペイン 3 本
1 段 × 2 タブ 1 段(154px) 5 ペイン 3 本(表示中のタブだけ)
3 段 3 段(671px) 9 ペイン 9 本

タブは表示しているタブだけを集計します。裏のタブは SQL を実行しません。
つまり「タブを増やしても表示は重くならない」ので、押し下げる高さは 1 段のまま、載せられる数字だけを増やせます

これは専用の一覧でも効きます。この記事の 9 ペイン(3 段)を 1 段 × 4 タブに組み替えると、
1 画面が読みやすくなり、開いた瞬間に走る SQL も 9 本から 3 本に減ります。

一覧ごとに出し分ける

共通はすべての一覧に出ます。ただし SFA パックの一覧はほとんどが絞り込み済みです。

一覧 絞り込み
全案件 なし
【個人】自分の案件 主担当 = ログインユーザー
【個人】活動予定(今週) 次回アクション日 = 今週 かつ 主担当 = 自分
【個人】活動漏れ 次回アクション日 <= 昨日 かつ 主担当 = 自分
稼働中の案件 商談フェーズ が 提案中 / 内示 / 商談予定
当月受注予定の案件 受注予定日 = 今月
翌月受注予定の案件 受注予定日 = 翌月

ダッシュボードの SQL は、一覧の絞り込みとは連動しません。 ペインは FROM APP4149 を自分で実行するだけなので、
「自分の案件 3 件」の一覧の上に「受注 8 件 / 4,080 万円」という全社の数字が並びます。
これは読み手が「この一覧の集計」と誤解する、いちばん危ない形です。短くしても誤解は消えません。

そのため、共通に置くペインはタイトルに範囲を書きます受注金額(全社))。配布しているサマリーもそうしてあります。

そこで一覧ごとに 3 通りから選びます。設定画面の左側の一覧で対象を選ぶと、その一覧の設定欄が出ます。

やり方 設定 向いている一覧
共通のサマリーを出す 共通(既定) 絞り込みの無い一覧(全案件)
非表示にする このビューで表示 のチェックを外す 個人の作業リスト、絞り込み一覧
専用の内容を作る 個別 に切り替えてペインを置く ダッシュボード専用の一覧、絞り込み条件を SQL 側にも書ける一覧

本記事の環境では次のようにしました。

一覧 ダッシュボード
【全社】ダッシュボード 専用の 9 ペイン(この記事の本文)
【全社】分析 専用のペイン(応用編のランキングなど)
全案件 共通(サマリー 1 段)
【個人】3 つ 非表示
稼働中の案件 専用の 3 ペイン(SQL 側も 商談フェーズ IN ('提案中','内示','商談予定') に揃える)
当月受注予定 専用の 2 ペイン(SQL 側も 受注予定日 = THIS_MONTH() に揃える)
翌月受注予定 非表示

「稼働中の案件」の専用ダッシュボードは、一覧と同じ条件で集計しているので ¥41,000,000 / 8 件
下に続くレコード表の件数と一致します。一致していることが読み手の信頼につながります。

ダッシュボードの下にレコード表が続く(一覧画面)

重い一覧を軽くしたいだけなら、非表示にせず折りたたむ手もあります。ダッシュボードのバーで畳むと、その一覧では SQL を実行しません。
畳んだ状態はブラウザに一覧ごとに記憶されます。最初から畳んでおきたい場合は、設定画面の 初期は折りたたむ をチェックします。
なお 共通 のビューは共通側の設定に従うため、このチェックは共通ダッシュボードか**個別**のときだけ現れます。


基本の 3 ステップ

ペインを 1 つ作る流れは、どの表示タイプでも同じです。

ステップ 1:ペインを追加する

設定画面ヘッダーの 「+ ペインを追加」 を押すとテンプレートギャラリーが開きます。13 種類の雛形があり、分類フィールド / 金額フィールド / 日付フィールド を選んでから選ぶと、SQL にフィールド名が差し込まれた状態でペインができます。

ペインは 1 タブあたり 12 枚まで置けます(タブを使わなければ 1 ダッシュボード 12 枚)。

テンプレートギャラリー(「+ ペインを追加」を押した状態)

ステップ 2:SQL を書く(基本タブ)

ペインをクリックするとペイン設定が開きます。基本タブの kSQL 欄に SQL を書くと、0.6 秒ほどで 「✓ 構文 OK」 が出て、プレビューが実データで描画されます。保存前に数字と見た目を確認できます。

ペイン設定はプレビューに重ねて出ます。編集したいペインが隠れたら、見出しを掴んで動かせます(置いた位置はブラウザが覚えます。見出しのダブルクリックで元の位置へ戻ります)。

ステップ 3:見せ方を整える(表示タブ)

表示タブで表示タイプごとのオプションを設定します。列の割り当て、数値フォーマット、条件付き書式などはここです。

ペイン設定(基本タブ — SQL と構文チェック)

ペインの動かし方

  • クリックで選択 → どこを掴んでもドラッグ移動。右下の角でリサイズ
  • Ctrl+クリック、または背景をドラッグ(範囲指定)で複数選択 → どれを掴んでも、並びを保ったまままとめて移動します。一緒に動く相手は移動中うすく表示されます
  • 動かし始めるとペイン設定は閉じます(重ねて出しているため、置いた先が隠れてしまうので)。用があればもう一度ペインをクリックしてください
  • 右クリックで複製・削除。削除は Ctrl+Z で戻せます。移動やサイズ変更も同じく Ctrl+Z

ペイン 1:KPI カード(受注金額 + 目標達成率)

一番目立たせたい数字はこれです。「受注フェーズの案件の売上合計」を大きく出し、目標 5,000 万円に対する達成率をプログレスバーで見せます。

SELECT SUM(売上) AS メイン値,
       50000000 AS 目標値
FROM APP4149
WHERE 商談フェーズ = '受注'

実行結果メイン値 = 40,800,000 / 目標値 = 50,000,000 → 達成率 81.6%

なぜこう書くのか

  • メイン値 目標値KPI カードの予約列名です。この名前で返すと、カードが自動的に「大きい数字」「達成率」として解釈します。
  • 予約列名は他に タイトル 基準値(増減率を自動計算)比較値 達成率 があります。英語環境向けに value / target / base なども使えます。
  • 列が 1 つだけなら AS メイン値 は省略できます(1 列目が自動的にメイン値になる)。SELECT COUNT(*) FROM APP4149 だけでも動きます。
  • 目標値を SQL に直接書く(50000000 AS 目標値)のは、目標管理アプリがまだ無い場合の簡易版です。

ペイン設定

タブ 項目 設定値
基本 表示タイプ KPI カード
基本 タイトル 受注金額
表示 フォントサイズ 特大
表示 達成率をプログレスバーで表示
表示 メイン値のフォーマット 接頭辞 ¥ / 3 桁区切り ✅ / 小数桁 0
表示 条件付きカラー… ①達成率 >= 100 → 良好(緑)・枠
②達成率 < 90 → 注意(黄)・枠

条件付きカラーは上から順に評価され、同じ適用先は後のルールが優先されます。今回は達成率 81.6% なので②が効き、カードの枠が黄色になります。

KPI カード(目標達成率とプログレスバー)


ペイン 2:KPI 複数行タイル(商談フェーズ別の金額)

KPI カードは「1 行 1 数字」だけでなく、結果の各行をミニタイルとして並べるモードがあります。フェーズ別のパイプラインを一目で見せるのに向いています。

SELECT 商談フェーズ AS タイトル,
       SUM(売上)  AS メイン値
FROM APP4149
WHERE 商談フェーズ <> ''
GROUP BY 商談フェーズ
ORDER BY メイン値 DESC

実行結果

タイトル メイン値
受注 40,800,000
提案中 28,250,000
内示 12,750,000

なぜこう書くのか

  • WHERE 商談フェーズ <> ''未入力を除外しています。SFA パックのサンプルデータにはフェーズ未設定のレコードが 4 件あり、これを入れると空ラベルのタイルが出てしまいます。
  • 予約列名(タイトル / メイン値)を使わず SELECT 商談フェーズ, SUM(売上) と書いても動きます。2 列の結果は「1 列目 = ラベル、2 列目 = 値」と自動解釈されるためです。予約列名を使うのは、意図を明示して読みやすくするためです。
  • ORDER BY メイン値 DESC のように、集計結果のエイリアスを ORDER BY に使うのは OK です(GROUP BY には使えません。後述)。

ペイン設定

タブ 項目 設定値
基本 表示タイプ KPI カード
表示 複数行をタイル表示
表示 最大タイル数 6
表示 メイン値のフォーマット 単位 / 3 桁区切り ✅ / 小数桁 0

単位に を選ぶと 40,800,0004,080万 と表示されます。金額系はこれだけで一気に読みやすくなります。

KPI タイル(受注 / 提案中 / 内示 の 3 タイル)


ペイン 3:マークダウン(説明文に数字を埋め込む)

「この数字は税抜です」のような注記を置けるペインです。SQL の結果を本文に差し込めます。

SELECT COUNT(*) AS 受注件数,
       ROUND(SUM(売上) / 10000) AS 受注金額万
FROM APP4149
WHERE 商談フェーズ = '受注'

本文(マークダウン)

### 今期サマリー

- 受注 **{{受注件数}}** 件 / **{{受注金額万}}** 万円
- 目標 5,000 万円に対する進捗は左の KPI カードを参照
- <span style="color:#c0392b">金額は税抜・受注フェーズのみ集計</span>

表示結果受注 8 件 / 4,080 万円

なぜこう書くのか

  • 本文の {{列名}}結果 1 行目の値に置き換わります。{{rowCount}} は行数になります。
  • 埋め込み値には数値フォーマットが効きません。 マークダウンペインには書式オプションが無いため、{{合計}} と書くと 40800000 がそのまま出ます。そこで SQL 側で ROUND(… / 10000) して万単位にしています。桁区切りが必要なら KPI カードを使うほうが簡単です。
  • style 属性は色・配置などの装飾系プロパティのみ有効です(position などは除去されます)。script タグなども自動的に取り除かれます。

ペイン設定

タブ 項目 設定値
基本 表示タイプ マークダウン
基本 マークダウン 上記の本文
基本 埋め込み用 SQL(任意) 上記の SQL
表示 文字サイズ 標準

埋め込み用 SQL は任意です。空にすると SQL を実行せず本文だけを即座に表示します。手順書やリンク集を置く用途ならこれで十分です。

マークダウンペイン(SQL の結果を本文に埋め込んだ状態)


ペイン 4:棒グラフ(商談フェーズ別の件数)

グラフの基本形です。「分類」と「数」を 1 つずつ返すだけで棒グラフになります。

SELECT 商談フェーズ,
       COUNT(*) AS 件数
FROM APP4149
WHERE 商談フェーズ <> ''
GROUP BY 商談フェーズ
ORDER BY 件数 DESC

実行結果:受注 8 件 / 提案中 6 件 / 内示 2 件

なぜこう書くのか

  • GROUP BY 商談フェーズ が「フェーズごとにまとめる」、COUNT(*) が「まとめた中の件数」です。これが集計の最小単位です。
  • 上位だけ見たいときは末尾に LIMIT 10 を足します。
  • 棒の幅や間隔はペイン側が自動調整するので、SQL 側で気にする必要はありません。

ペイン設定

タブ 項目 設定値
基本 表示タイプ
表示 凡例の位置 非表示
表示 列の割り当て → X 軸(カテゴリ) 商談フェーズ
表示 列の割り当て → 系列列(ワイド形式) 件数

列の割り当ては未指定でも自動判定されます。明示するのは、列が増えたときに意図しない列が拾われないようにするためです。

棒グラフ


ペイン 5:ドーナツグラフ(提案商品別の売上構成比)

構成比を見せたいときはドーナツです。SQL は棒グラフとまったく同じ形で、表示タイプを変えるだけです。

SELECT 提案商品,
       SUM(売上) AS 売上
FROM APP4149
WHERE 提案商品 <> ''
GROUP BY 提案商品
ORDER BY 売上 DESC

実行結果

提案商品 売上
商品C 35,250,000
商品B 25,950,000
その他 10,400,000
商品A 10,200,000

なぜこう書くのか

  • 構成比(%)を SQL で計算する必要はありません。 割合の計算と「上位 N + その他」への集約はペイン側が行います。
  • 逆に言うと、SELECT 商品, 金額 * 100 / 全体金額 のような書き方はできません。「全体の合計」を各行に持ってくる手段(SUM(...) OVER () や、集計を含むスカラーサブクエリ)が無いためです。割合はドーナツに任せてください。
    一時テーブルと SET @変数 を使う複文でも同じです(サブクエリから受け取った変数は算術に使えません)。
    なお順位付けのウィンドウ関数(RANK() など)は使えます。応用編の**「ランキングを出す(ウィンドウ関数)」**を参照してください。

ペイン設定

タブ 項目 設定値
基本 表示タイプ ドーナツ
表示 凡例の位置
表示 上位 N + その他集約 8
表示 列の割り当て → X 軸(カテゴリ) 提案商品
表示 列の割り当て → 値列 売上
表示 値のフォーマット 単位 / 3 桁区切り ✅ / 小数桁 0

ドーナツグラフ(凡例つき)


ペイン 6:複合グラフ(月次の売上と件数)

金額(棒)と件数(線)のように、単位の違う 2 つの指標を重ねたいときは複合グラフを使います。2 軸が使えるのはこの表示タイプだけです。

SELECT DATE_FORMAT(受注予定日, '%Y-%m') AS 年月,
       SUM(売上) AS 売上,
       COUNT(*)  AS 件数
FROM APP4149
WHERE 受注予定日 <> ''
GROUP BY DATE_FORMAT(受注予定日, '%Y-%m')
ORDER BY 年月

実行結果(9 か月ぶん)

年月 売上 件数
2026-01 3,600,000 1
2026-06 9,500,000 3
2026-07 23,400,000 4
2026-08 19,000,000 3
2026-09 3,600,000 1

9 ペインのうち、月次の値が変わるのはこのペインだけです。ほかの 8 つは日付を使っていないため、
前提に書いた日付の振り直しをしてもしなくても同じ数字になります。
ここに出る年月は、あなたが振り直した基準日によって変わります。

なぜこう書くのか

  • DATE_FORMAT(受注予定日, '%Y-%m') で日付を「年月」の文字列にまとめます。これが月次集計の定番です。週次は '%G-%v'(ISO 週)、四半期は QUARTER(受注予定日) を使います。
  • GROUP BY には式をそのまま書きます。 GROUP BY 年月(エイリアス)でも動きますが、同名の実フィールドがあるとそちらが優先されるなど紛らわしいため、式で書くのが安全です。
  • 1 回の GROUP BY で 2 つの集計列を返せば、棒と線の元データが揃います。

ペイン設定

タブ 項目 設定値
基本 表示タイプ 複合
表示 凡例の位置 上(既定)
表示 列の割り当て → X 軸(カテゴリ) 年月
表示 列の割り当て → 棒の系列 売上
表示 列の割り当て → 線の系列 件数
表示 値のフォーマット 単位 / 3 桁区切り ✅ / 小数桁 0

複合グラフ(棒 + 線の 2 軸)


ペイン 7:横棒グラフ(業種別売上 = 別アプリと結合)

ここからが SFA パックらしい使い方です。案件管理には「業種」がありません。業種は顧客管理アプリ側の情報です。そこで JOIN で 2 つのアプリをつなぎます。

SELECT c.業種,
       SUM(d.売上) AS 売上
FROM APP4149 d
JOIN APP4148 c ON d.顧客No_ = c.顧客No
GROUP BY c.業種
ORDER BY 売上 DESC

実行結果

業種 売上
運輸・通信業 47,050,000
製造業 15,750,000
不動産業 13,600,000
金融業 5,400,000

なぜこう書くのか

  • FROM APP4149 dd は**別名(エイリアス)**です。d.売上 のように「どちらのアプリの列か」を書き分けるために付けます。
  • 結合キーは案件管理の 顧客No_(数値)と顧客管理の 顧客No(レコード番号)です。フィールド名はフィールドコードを使います。ラベルは「顧客No.」でもコードは 顧客No_ のように末尾が違うことがあるので、アプリの設定でコードを確認してください。
  • JOIN で使う結合キーは数値にしておくと速くなります。 上の SQL では、案件 20 件を読んだあと顧客管理には 顧客No in ("1","2","3",…) という絞り込みが自動的に送られ、215 件のうち 10 件しか取得していません。文字列や日付の条件では、この絞り込みが効かず両アプリとも全件取得になります。

ペイン設定

タブ 項目 設定値
基本 表示タイプ 横棒
表示 凡例の位置 非表示
表示 列の割り当て → X 軸(カテゴリ) 業種
表示 列の割り当て → 系列列(ワイド形式) 売上
表示 値のフォーマット 単位 / 3 桁区切り ✅ / 小数桁 0

項目名が長いときは横棒が読みやすい。 「卸売業、小売業、飲食店」のような長いラベルは、縦棒だと文字が斜めになったり省略されたりします。

横棒グラフ(業種別売上)


ペイン 8:表(顧客ランク別実績・ヒートマップ + 集計行)

同じ JOIN のデータを、今度は表で見せます。表は「集計行」「条件付き書式」「ヒートマップ」がペイン側の機能なので、SQL は素直に集計するだけです。

SELECT c.顧客ランク AS ランク,
       COUNT(*)     AS 件数,
       SUM(d.売上)  AS 売上
FROM APP4149 d
JOIN APP4148 c ON d.顧客No_ = c.顧客No
GROUP BY c.顧客ランク
ORDER BY c.顧客ランク

実行結果

ランク 件数 売上
A 13 41,650,000
B 5 29,350,000
C 1 3,600,000
D 1 7,200,000
合計 20 81,800,000

(「合計」の行は SQL では出していません。ペインの集計行機能です。)

なぜこう書くのか

  • AS ランク のように別名を付けると、表の見出しがその名前になります。列の表示名は設定でも変えられますが、SQL 側で付けておくとグラフ・CSV でも同じ名前になります。
  • ORDER BY c.顧客ランク で A→D の順に固定しています。並び順を指定しないと表示順が安定しません。

ペイン設定

タブ 項目 設定値
基本 表示タイプ
表示 表示密度 コンパクト
表示 列の設定… → ランク 80 / 揃え 中央
表示 列の設定… → 件数 90 / 揃え / 集計 合計
表示 列の設定… → 売上 揃え / フォーマット 種別 数値・接頭辞 ¥・3 桁区切り ✅・小数桁 0 / 集計 合計
表示 条件付き書式… 種別 ヒートマップ / 列 売上

表(ヒートマップ + 合計行)


ペイン 9:明細表(レコードへのリンク + 条件付き書式)

最後は明細です。$id を SELECT に入れると、各行の左端に「レコードを開く」アイコンが出て、クリックでその案件レコードを別タブで開けます。

SELECT $id, 案件名, 会社名, 商談フェーズ, 確度, 売上, 受注予定日
FROM APP4149
WHERE 商談フェーズ <> ''
ORDER BY 売上 DESC
LIMIT 100

実行結果(16 件・上位のみ抜粋)

案件名 会社名 フェーズ 確度 売上
東京キントーンフェス向け集客サービス 株式会社テクノロジーサービス 提案中 40% 7,200,000
クラウドERP刷新プロジェクト 株式会社倉本インターナショナル 受注 80% 7,100,000
社内ポータル刷新PJ 株式会社橘川ケミカル 受注 100% 6,700,000

なぜこう書くのか

  • 明細表は行数が増えやすい唯一のペインです。 必ず LIMIT を付けて、取得上限(既定 10,000 件)より十分小さく保ちます。
  • $id はレコードの内部 ID です。表示する必要はないので、設定で非表示にしてもリンクは残ります
  • 行全体をクリックしても遷移しません。誤操作を防ぐため、左端のアイコンをクリックしたときだけ開きます。

ペイン設定

タブ 項目 設定値
基本 表示タイプ
表示 表示密度 コンパクト
表示 列の設定… → $id 表示 ☐(非表示)
表示 列の設定… → 案件名 / 会社名 300 / 240
表示 列の設定… → 売上 揃え / フォーマット 接頭辞 ¥・3 桁区切り ✅ / 集計 合計
表示 列の設定… → 受注予定日 揃え 中央 / フォーマット 種別 日付YYYY/MM/DD
表示 条件付き書式… 売上 >= 6000000 → 良好(緑)・セル
商談フェーズ = 内示 → 注意(黄)・行全体
表示 初期ソート… 売上 降順

明細表(レコードを開くアイコン・緑のセル・黄色い行が見える状態)


応用編:もう一歩使いこなす

この節で作るペインも、設定 JSON にまとめてありますsfa-案件管理-分析.json(付録)。

本文の 9 ペインとは別のダッシュボードなので、専用の一覧に入れます。取り込み方が違う点に注意してください。

  1. ツールビューへ取り込むsfa-案件管理-分析.json を選ぶ
  2. 確認ダイアログで取り込み先【全社】分析 を選び、アプリ番号を変換して取り込む

同じツールの中にある インポート設定を丸ごと置き換えるので、そちらで取り込むと本文の 9 ペインが消えます。
「ビューへ取り込む」は、取り込み先に選んだ一覧だけを書き換えます。

期間を「今月」に絞る(相対日付)

THIS_MONTH() などの相対日付関数は、kintone の検索条件にそのまま変換されてサーバー側で絞り込まれます。つまり、取得するレコード数そのものが減るので速くなります。

-- 顧客管理に今月登録された件数
SELECT COUNT(*) AS メイン値
FROM APP4148
WHERE 作成日時 = THIS_MONTH()

実際に kintone へ送られる条件は 作成日時 = THIS_MONTH() で、215 件のうち 6 件しか取得しません

今月と先月を並べて前月比を出すこともできます。

SELECT
  SUM(CASE WHEN YEAR(作成日時) * 12 + MONTH(作成日時)
           = YEAR(CURRENT_DATE()) * 12 + MONTH(CURRENT_DATE())     THEN 1 ELSE 0 END) AS メイン値,
  SUM(CASE WHEN YEAR(作成日時) * 12 + MONTH(作成日時)
           = YEAR(CURRENT_DATE()) * 12 + MONTH(CURRENT_DATE()) - 1 THEN 1 ELSE 0 END) AS 基準値
FROM APP4148
WHERE 作成日時 = THIS_MONTH() OR 作成日時 = LAST_MONTH()

基準値 を返すと、KPI カードが増減率(▲▼ %)を自動計算します。

ポイント

  • 月の比較に YEAR() * 12 + MONTH() を使うのは、年をまたいでも正しく「1 か月前」になるからです(12 月の前月 = 前年 11 月、ではなく前年 12 月…といった間違いを防げます)。
  • DATE_ADD / DATE_SUB / INTERVAL は使えません。日付の足し算・引き算はこの数値演算で代用します。
  • 相対日付関数は WHERE 句専用です。SELECTCASE の中では使えないので、上のように YEAR() / MONTH() / CURRENT_DATE() を使います。
  • JOIN を含む SQL でも相対日付関数を使えます。 しかも AND でつなげば、両方のアプリがそれぞれサーバー側で絞り込まれます
-- 案件は今年ぶん、顧客は A ランクだけをサーバーから取得する
SELECT c.業種, COUNT(*) AS 件数, SUM(d.売上) AS 売上
FROM APP4149 d
JOIN APP4148 c ON d.顧客No_ = c.顧客No
WHERE d.作成日時 = THIS_YEAR()   -- 案件管理へ送られる
  AND c.顧客ランク in ('A')      -- 顧客管理へ送られる
GROUP BY c.業種

実測では、絞り込み無しなら 235 件取得するところが 79 件で済みました。

選択肢のフィールドは = より in のほうが速い。 顧客ランク = 'A' でも正しい結果は出ますが、
この形は kintone のクエリに変換されず全件取得になります(顧客 215 件で実測)。
in ('A') と書くと 59 件の取得に減ります。ドロップダウン・ラジオボタンは in を使ってください。

「自分の案件だけ」を出す

LOGINUSER() を使うと、見ている人ごとに内容が変わるマイダッシュボードになります。

SELECT $id, 案件名, 商談フェーズ, 売上
FROM APP4149
WHERE 主担当 in (LOGINUSER())
ORDER BY 売上 DESC
LIMIT 200

ユーザー選択・作成者・更新者・チェックボックス・複数選択のフィールドには = が使えません。 必ず in / not in を使ってください(= はエラーになります)。ドロップダウン・ラジオボタン・ステータスは = で問題ありません。

ランキングを出す(ウィンドウ関数)

「売上上位 5 社」「フェーズごとの上位 3 社」のように順位を列として持ちたいときは、ウィンドウ関数が使えます。

使えるのは ROW_NUMBER() / RANK() / DENSE_RANK() の 3 つです。

関数 同点のときの挙動
ROW_NUMBER() 同点でも 1, 2, 3 … と連番(並び順で決まる)
RANK() 同点は同じ順位。次は飛ぶ(1, 2, 2, 4)
DENSE_RANK() 同点は同じ順位。次は飛ばない(1, 2, 2, 3)

書き方は 1 つだけ覚えれば十分です。

RANK() OVER (PARTITION BY グループにする列 ORDER BY 並べる列 DESC) AS 順位
  • PARTITION BY は「グループごとに順位を振り直す」指定です。省略すると全体で 1 位から振ります。
  • OVER (…)AS 別名 は省略できません。 どちらを抜いてもエラーになります。

会社別 売上ランキング(Top 5)

集計とランク付けは同じ SELECT に書けませんWITH(CTE)で 2 段に分けます。

WITH 集計 AS (
  SELECT 会社名, COUNT(*) AS 件数, SUM(売上) AS 売上
  FROM APP4149
  GROUP BY 会社名
),
順位付き AS (
  SELECT 会社名, 件数, 売上,
         RANK() OVER (ORDER BY 売上 DESC) AS 順位
  FROM 集計
)
SELECT 順位, 会社名, 件数, 売上
FROM 順位付き
WHERE 順位 <= 5
ORDER BY 順位

実行結果

順位 会社名 件数 売上
1 株式会社サイボウズ商事 5 20,700,000
2 株式会社キントーンシステムズ 3 15,550,000
3 株式会社倉本インターナショナル 2 13,600,000
4 篠村食品株式会社 2 9,050,000
5 株式会社テクノロジーサービス 1 7,200,000

3 段構えにしている理由

  1. 集計 … 会社ごとに合計する(ここでは順位を付けない)
  2. 順位付き … 集計結果に順位を振る(ここでは絞り込まない)
  3. 最後の SELECTWHERE 順位 <= 5 で上位だけ残す

ORDER BY 順位 を最後に付けている点に注意してください。これが無いと順位順に並びません(順位は列として計算されるだけで、行の並び替えは別の処理だからです)。

単純な上位 N なら ORDER BY 売上 DESC LIMIT 5 で十分です(ペイン 9 と同じ形)。ウィンドウ関数の出番は、順位を数字として表示したいときと、次の「グループごとの上位」です。

フェーズごとの上位 3 社

PARTITION BY を足すだけです。LIMIT ではこれはできません。

WITH 集計 AS (
  SELECT 商談フェーズ, 会社名, SUM(売上) AS 売上
  FROM APP4149
  WHERE 商談フェーズ <> ''
  GROUP BY 商談フェーズ, 会社名
),
順位付き AS (
  SELECT 商談フェーズ, 会社名, 売上,
         RANK() OVER (PARTITION BY 商談フェーズ ORDER BY 売上 DESC) AS 順位
  FROM 集計
)
SELECT 商談フェーズ, 順位, 会社名, 売上
FROM 順位付き
WHERE 順位 <= 3
ORDER BY 商談フェーズ, 順位

実行結果(8 行)

商談フェーズ 順位 会社名 売上
受注 1 株式会社キントーンシステムズ 9,300,000
受注 2 株式会社サイボウズ商事 8,100,000
受注 3 株式会社倉本インターナショナル 7,100,000
提案中 1 株式会社サイボウズ商事 12,600,000
内示 1 株式会社倉本インターナショナル 6,500,000

ペイン設定(表タイプで使う場合)

タブ 項目 設定値
基本 表示タイプ
表示 表示密度 コンパクト
表示 列の設定… → 順位 60 / 揃え
表示 列の設定… → 売上 揃え / フォーマット 接頭辞 ¥・3 桁区切り ✅
表示 条件付き書式… 順位 = 1 → 良好(緑)・行全体

制約

  • 集計(SUM など)や GROUP BY同じ SELECT には書けません。上のように CTE で分けます。
  • OVER (ORDER BY …) の中に集計関数は書けません(OVER (ORDER BY SUM(売上)) は不可)。
  • JOIN とは併用できます。また WHERE の相対日付関数もそのまま効きます(サーバー側の絞り込みも維持されます)。

ランキング表(順位列つき・1 位の行が緑)

活動履歴を集計する

活動履歴アプリ(APP4147)を使うと「動けているか」が見えます。

-- 対応種別ごとの活動件数(棒グラフ向け)
SELECT 対応種別,
       COUNT(*) AS 件数
FROM APP4147
WHERE 対応種別 <> ''
GROUP BY 対応種別
ORDER BY 件数 DESC

実行結果:商談(2 回目以降)6 件 / 商談(初回)6 件 / 電話 4 件 / メール 2 件

-- 案件ごとの活動量(表向け・案件管理と結合)
SELECT d.案件名, d.商談フェーズ, d.売上,
       COUNT(*) AS 活動件数
FROM APP4147 a
JOIN APP4149 d ON a.案件No = d.案件No_
GROUP BY d.案件名, d.商談フェーズ, d.売上
ORDER BY 活動件数 DESC
LIMIT 10

活動が少ないのに金額が大きい案件を、条件付き書式で目立たせるといった使い方ができます。


つまずきポイント(実際に出るエラーと対処)

エラーはペイン内に [コード] メッセージ の形式で表示されます。長いメッセージは省略されるので、切れているときは 詳細 を押してください。全文がポップアップで開き、そのままコピーできます。

よく出るものを挙げます。

GROUP BY alias 件数 depends on aggregate evaluation

-- ✕ 集計結果のエイリアスは GROUP BY に使えない
SELECT 商談フェーズ, COUNT(*) AS 件数 FROM APP4149 GROUP BY 件数

GROUP BY には集計する前の値(フィールドや式)を書きます。ORDER BY にはエイリアスを使えます。

THIS_YEAR: WHERE_RELATIVE_DATE_REQUIRES_EXACT_PUSHDOWN

JOIN と相対日付は併用できますが、次の 3 つの形だけはエラーになります。

-- ✕ ① LEFT / RIGHT JOIN(外部結合)
SELECT COUNT(*) FROM APP4149 d LEFT JOIN APP4148 c ON d.顧客No_ = c.顧客No
WHERE d.作成日時 = THIS_YEAR()

-- ✕ ② アプリをまたぐ OR
WHERE d.作成日時 = THIS_MONTH() OR c.作成日時 = THIS_MONTH()

-- ✕ ③ WITH(CTE)を JOIN の入力にした形
WITH t AS (SELECT 顧客No_, 作成日時 FROM APP4149)
SELECT COUNT(*) FROM t JOIN APP4148 c ON t.顧客No_ = c.顧客No
WHERE t.作成日時 = THIS_YEAR()

②は AND に変える、①③は DATE_FORMAT() を使ったローカル判定に書き換えます。
いずれもレコードを取得する前にエラーになるので、誤った結果が出ることはありません。

values of type USER_SELECT cannot be compared

-- ✕ ユーザー選択フィールドに = は使えない
SELECT COUNT(*) FROM APP4149 WHERE 主担当 = 'rex0220'

WHERE 主担当 in (LOGINUSER()) の形にします。

ウィンドウ関数は GROUP BY / 集計関数と同じ SELECT では使用できません

-- ✕ 集計とランク付けを 1 つの SELECT に混ぜられない
SELECT 商談フェーズ, SUM(売上) AS 売上,
       RANK() OVER (ORDER BY 商談フェーズ) AS 順位
FROM APP4149 GROUP BY 商談フェーズ

WITH(CTE)で集計を済ませてから、外側でランクを付けます(応用編の**「ランキングを出す(ウィンドウ関数)」**)。

ウィンドウ関数には AS alias が必要です / RANK には OVER (...) が必要です

ウィンドウ関数は RANK() OVER (…) AS 順位 の形が必須です。OVER (…)AS 別名 はどちらも省略できません。

集計算術式には集計関数または数値が必要です

-- ✕ 集計を含むスカラーサブクエリは使えない
SELECT 提案商品, SUM(売上) * 100 / (SELECT SUM(売上) FROM APP4149) AS 構成比
FROM APP4149 GROUP BY 提案商品

「全体に対する割合」は SQL で出さず、ドーナツグラフに任せてください。

取得上限に達しました

集計クエリが取得上限に達した場合、中途半端な合計を出さずにエラーにします(fail-closed)。WHERE で期間や条件を絞るか、詳細タブの「取得上限」を上げてください(500〜50,000 件・既定 10,000。10,000 超は取得に時間がかかるため、絞り込みが先です)。ランキングや直近一覧のように一部しか見ないペインは、逆に上限を下げると軽くなります。

数字が合わない気がするとき

  • 未入力のレコードが混ざっていませんか。 WHERE 商談フェーズ <> '' のように除外すると、空ラベルの行やタイルが消えます。
  • 確度 のようなドロップダウンは文字列です。 100% 20% 40% … を並べ替えると 20%, 40%, 60%, 80%, 100% のように文字列順(100% が最後)になります。数値として扱いたい項目は、kintone 側で数値フィールドにしておくのが確実です。
  • ダッシュボードはログインユーザーの権限の範囲で集計します。閲覧権限のないレコードは数に入りません。

仕上げ:見せ方を整える

作った 9 個のペインは、設定画面のレイアウト機能で並べます。

  • レイアウトドロップダウンから「均等分割(1〜6 列)」「上 2・下 1」「KPI 行 3 + 下 2」などのプリセットを適用できます。
  • ペインを動かして行間が空いてしまったら、**「空き行を詰める」**ボタンで詰まります(左右の位置関係は変わりません)。
  • 編集ステージは基準幅(1280 / 1536 / 1920px)の仮想キャンバスなので、編集に使う PC の画面サイズで配置が変わりません。
  • 表示サイズで文字と余白の倍率をまとめて変えられます(小(100%) / 標準(110%) / 大(125%) / 特大(150%)。既定は 標準(110%))。ブラウザのズームと違い、ペインの枠に収まったまま大きくなります。
  • グラフの軸目盛りは既定で整数だけです。件数のグラフに 0.75 のような目盛りが出ません。率や平均を描くペインでは、表示タブの 軸に小数を許す を有効にしてください。

設定画面のレイアウト編集(ドラッグ中のガイド線)

タブで分ける(9 ペインを 1 段 × 4 タブに)

9 ペインを 1 画面に並べると 3 段(671px)になります。タブに分けるとどのタブも 1 段で読み切れ、
開いた瞬間に走る SQL も 9 本から 3 本に減ります。裏のタブは表示するまで実行しません。

作り方は 3 つの操作だけです。

  1. ステージ上の + タブ で空のタブを足す(押した時点で 1 枚増えます)
  2. タブの見出しをダブルクリックして名前を付ける(サマリー / グラフ / 集計 / 明細
  3. ペインを**右クリック → 「タブへ移動」**で送る

タブ自体を右クリックすると複製もできます。「同じ構成で条件だけ違うタブ」を作るときに使います。
見出しの数字はそのタブのペイン枚数です(切り替えても中身が残っていることが分かります)。

1 段しか置けない標準の一覧でこそ効きます。 共通ダッシュボードを 1 段 × 2 タブにすると、
レコードを押し下げる高さは変えずに、見られる数字が増えます(前述)。

タブは 1 ダッシュボードあたり 6 枚まで、ペインは 1 タブあたり 12 枚までです。

タブで分けたダッシュボード(ダッシュボード名の隣にタブ列)

一覧画面側では、閲覧する人が次の操作を行えます。

  • テーマ切り替え:6 テーマから選択。選択はブラウザに記憶され、管理者設定より優先されます。
  • 表示サイズ:管理者が決めた既定に対して、見る人が自分の PC だけ大きさを変えられます(設定に従う に戻せます)。適正な倍率は人によって違うので、アプリごと・端末ごとに残ります。
  • たたむ(ダッシュボード名・タブ列の隣):ダッシュボードを細いバーに畳みます。折りたたみ中は SQL を実行しませんので、重いダッシュボードは普段閉じておけます。
  • フル画面表示:ブラウザ全面に表示(Esc で復帰)。朝礼やレビューで投影するときに便利です。
  • ペインの拡大表示:1 枚だけを全面表示(再クエリなし)。

フル画面表示(ダッシュボード名つき)


まとめ

  • ダッシュボードは 「集計して小さく返す SQL」+「見せ方はペイン設定」 の分担で作ります。構成比・集計行・条件付き書式・数値フォーマットは SQL で頑張らないのがコツです。
  • SFA パックなら、案件管理を軸に、顧客管理・活動履歴を JOIN でつなぐと一気に情報量が増えます。結合キーは数値にしておくと速くなります。
  • 期間の絞り込みは相対日付関数が第一候補。JOIN と併用でき、AND でつなげば両方のアプリが絞り込まれます。ユーザー選択には = が使えない、選択肢のフィールドは in のほうが速い、といった約束事があります。
  • 設定画面のプレビューは実データなので、保存前に必ず数字を確認できます。まずはテンプレートギャラリーから 1 つ追加して、SQL を書き換えてみてください。

関連記事

  • kSQL Dashboard Pro プラグイン(機能一覧)
  • kSQL(アプリを SQL ライクに操作するプラグイン)
  • 無償版 kSQL Dashboard

付録 — 配布ファイル(コピーして保存)

本文で使う設定 JSON とスクリプトの全文です。使い方は 3 手順です。

  1. 折りたたみを開き、コードブロック右上のコピーボタンでコピー
  2. テキストエディター(メモ帳等)に貼り付け、見出しのファイル名どおりに保存(文字コード UTF-8)
  3. 設定画面の ツール → インポート / ビューへ取り込む でそのファイルを選択

SQL 中の APP4149 / APP4148 は、取り込み時の確認ダイアログ「アプリ番号の変換」でご自身の環境の番号へ変換できます(JSON を手で書き換える必要はありません)。

sfa-案件管理-サマリー.json — 共通サマリー 1 段(インポートで全一覧に)
{
  "schemaVersion": 2,
  "edition": "pro",
  "views": {},
  "common": {
    "view": {
      "name": "営業サマリー",
      "layout": [
        {
          "i": "pane1",
          "x": 0,
          "y": 0,
          "w": 14,
          "h": 3
        },
        {
          "i": "pane2",
          "x": 14,
          "y": 0,
          "w": 26,
          "h": 3
        },
        {
          "i": "pane3",
          "x": 40,
          "y": 0,
          "w": 20,
          "h": 3
        }
      ],
      "panes": [
        {
          "id": "pane1",
          "type": "kpi",
          "title": "受注金額(全社)",
          "sql": "SELECT SUM(売上) AS メイン値,\n       50000000 AS 目標値\nFROM APP4149\nWHERE 商談フェーズ = '受注'",
          "options": {
            "fontSize": "xlarge",
            "goalBar": true,
            "format": {
              "prefix": "¥",
              "thousandSep": true,
              "decimals": 0
            },
            "rules": [
              {
                "target": "rate",
                "op": ">=",
                "value": 100,
                "tone": "good",
                "apply": "border"
              },
              {
                "target": "rate",
                "op": "<",
                "value": 90,
                "tone": "warn",
                "apply": "border"
              }
            ]
          }
        },
        {
          "id": "pane2",
          "type": "kpi",
          "title": "商談フェーズ別 金額(全社)",
          "sql": "SELECT 商談フェーズ AS タイトル,\n       SUM(売上)  AS メイン値\nFROM APP4149\nWHERE 商談フェーズ <> ''\nGROUP BY 商談フェーズ\nORDER BY メイン値 DESC",
          "options": {
            "multi": true,
            "maxTiles": 6,
            "format": {
              "scale": "man",
              "decimals": 0,
              "thousandSep": true
            }
          }
        },
        {
          "id": "pane3",
          "type": "markdown",
          "title": "今期サマリー(全社)",
          "sql": "SELECT COUNT(*) AS 受注件数,\n       ROUND(SUM(売上) / 10000) AS 受注金額万\nFROM APP4149\nWHERE 商談フェーズ = '受注'",
          "options": {
            "markdown": "- 受注 **{{受注件数}}** 件 / **{{受注金額万}}** 万円\n- **全社の数字です**(一覧の絞り込みとは連動しません)",
            "fontSize": "normal"
          }
        }
      ]
    }
  },
  "deployOnSave": true
}
sfa-案件管理-dashboard.json — 本文の 9 ペイン(ビューへ取り込む)
{
  "schemaVersion": 2,
  "edition": "pro",
  "views": {},
  "common": {
    "theme": "light",
    "csvExport": true,
    "view": {
      "name": "営業ダッシュボード",
      "layout": [
        { "i": "pane1", "x": 0, "y": 0, "w": 14, "h": 3 },
        { "i": "pane2", "x": 14, "y": 0, "w": 26, "h": 3 },
        { "i": "pane3", "x": 40, "y": 0, "w": 20, "h": 3 },
        { "i": "pane4", "x": 0, "y": 3, "w": 20, "h": 5 },
        { "i": "pane5", "x": 20, "y": 3, "w": 20, "h": 5 },
        { "i": "pane6", "x": 40, "y": 3, "w": 20, "h": 5 },
        { "i": "pane7", "x": 0, "y": 8, "w": 30, "h": 5 },
        { "i": "pane8", "x": 30, "y": 8, "w": 30, "h": 5 },
        { "i": "pane9", "x": 0, "y": 13, "w": 60, "h": 6 }
      ],
      "panes": [
        {
          "id": "pane1",
          "type": "kpi",
          "title": "受注金額",
          "sql": "SELECT SUM(売上) AS メイン値,\n       50000000 AS 目標値\nFROM APP4149\nWHERE 商談フェーズ = '受注'",
          "options": {
            "fontSize": "xlarge",
            "goalBar": true,
            "format": { "prefix": "¥", "thousandSep": true, "decimals": 0 },
            "rules": [
              { "target": "rate", "op": ">=", "value": 100, "tone": "good", "apply": "border" },
              { "target": "rate", "op": "<", "value": 90, "tone": "warn", "apply": "border" }
            ]
          }
        },
        {
          "id": "pane2",
          "type": "kpi",
          "title": "商談フェーズ別 金額",
          "sql": "SELECT 商談フェーズ AS タイトル,\n       SUM(売上)  AS メイン値\nFROM APP4149\nWHERE 商談フェーズ <> ''\nGROUP BY 商談フェーズ\nORDER BY メイン値 DESC",
          "options": {
            "multi": true,
            "maxTiles": 6,
            "format": { "scale": "man", "decimals": 0, "thousandSep": true }
          }
        },
        {
          "id": "pane3",
          "type": "markdown",
          "title": "サマリー",
          "sql": "SELECT COUNT(*) AS 受注件数,\n       ROUND(SUM(売上) / 10000) AS 受注金額万\nFROM APP4149\nWHERE 商談フェーズ = '受注'",
          "options": {
            "markdown": "### 今期サマリー\n\n- 受注 **{{受注件数}}** 件 / **{{受注金額万}}** 万円\n- 目標 5,000 万円に対する進捗は左の KPI カードを参照\n- <span style=\"color:#c0392b\">金額は税抜・受注フェーズのみ集計</span>",
            "fontSize": "normal"
          }
        },
        {
          "id": "pane4",
          "type": "bar",
          "title": "商談フェーズ別 件数",
          "sql": "SELECT 商談フェーズ,\n       COUNT(*) AS 件数\nFROM APP4149\nWHERE 商談フェーズ <> ''\nGROUP BY 商談フェーズ\nORDER BY 件数 DESC",
          "options": {
            "legend": "none",
            "mapping": { "x": "商談フェーズ", "valueColumns": ["件数"] }
          }
        },
        {
          "id": "pane5",
          "type": "donut",
          "title": "提案商品別 売上構成比",
          "sql": "SELECT 提案商品,\n       SUM(売上) AS 売上\nFROM APP4149\nWHERE 提案商品 <> ''\nGROUP BY 提案商品\nORDER BY 売上 DESC",
          "options": {
            "legend": "right",
            "topN": 8,
            "mapping": { "x": "提案商品", "value": "売上" },
            "valueFormat": { "scale": "man", "decimals": 0, "thousandSep": true }
          }
        },
        {
          "id": "pane6",
          "type": "combo",
          "title": "月次 売上と件数",
          "sql": "SELECT DATE_FORMAT(受注予定日, '%Y-%m') AS 年月,\n       SUM(売上) AS 売上,\n       COUNT(*)  AS 件数\nFROM APP4149\nWHERE 受注予定日 <> ''\nGROUP BY DATE_FORMAT(受注予定日, '%Y-%m')\nORDER BY 年月",
          "options": {
            "legend": "top",
            "mapping": { "x": "年月" },
            "bars": ["売上"],
            "lines": ["件数"],
            "valueFormat": { "scale": "man", "decimals": 0, "thousandSep": true }
          }
        },
        {
          "id": "pane7",
          "type": "barH",
          "title": "業種別 売上(顧客管理と結合)",
          "sql": "SELECT c.業種,\n       SUM(d.売上) AS 売上\nFROM APP4149 d\nJOIN APP4148 c ON d.顧客No_ = c.顧客No\nGROUP BY c.業種\nORDER BY 売上 DESC",
          "options": {
            "legend": "none",
            "mapping": { "x": "業種", "valueColumns": ["売上"] },
            "valueFormat": { "scale": "man", "decimals": 0, "thousandSep": true }
          }
        },
        {
          "id": "pane8",
          "type": "table",
          "title": "顧客ランク別 実績(顧客管理と結合)",
          "sql": "SELECT c.顧客ランク AS ランク,\n       COUNT(*)     AS 件数,\n       SUM(d.売上)  AS 売上\nFROM APP4149 d\nJOIN APP4148 c ON d.顧客No_ = c.顧客No\nGROUP BY c.顧客ランク\nORDER BY c.顧客ランク",
          "options": {
            "density": "compact",
            "columns": {
              "ランク": { "width": 80, "align": "center" },
              "件数": { "width": 90, "align": "right" },
              "売上": {
                "align": "right",
                "format": { "kind": "number", "prefix": "¥", "thousandSep": true, "decimals": 0 }
              }
            },
            "totals": { "件数": "sum", "売上": "sum" },
            "rules": [{ "kind": "heatmap", "column": "売上" }]
          }
        },
        {
          "id": "pane9",
          "type": "table",
          "title": "案件明細(金額順)",
          "sql": "SELECT $id, 案件名, 会社名, 商談フェーズ, 確度, 売上, 受注予定日\nFROM APP4149\nWHERE 商談フェーズ <> ''\nORDER BY 売上 DESC\nLIMIT 100",
          "options": {
            "density": "compact",
            "columns": {
              "$id": { "hidden": true },
              "案件名": { "width": 300 },
              "会社名": { "width": 240 },
              "商談フェーズ": { "width": 110, "align": "center" },
              "確度": { "width": 80, "align": "center" },
              "売上": {
                "align": "right",
                "format": { "kind": "number", "prefix": "¥", "thousandSep": true, "decimals": 0 }
              },
              "受注予定日": {
                "width": 120,
                "align": "center",
                "format": { "kind": "date", "pattern": "YYYY/MM/DD" }
              }
            },
            "totals": { "売上": "sum" },
            "rules": [
              { "kind": "threshold", "column": "売上", "op": ">=", "value": 6000000, "tone": "good" },
              { "kind": "threshold", "column": "商談フェーズ", "op": "=", "value": "内示", "tone": "warn", "scope": "row" }
            ],
            "initialSort": [{ "column": "売上", "desc": true }]
          }
        }
      ]
    }
  },
  "deployOnSave": true
}
sfa-案件管理-分析.json — 応用編のランキングなど(ビューへ取り込む)
{
  "schemaVersion": 2,
  "edition": "pro",
  "views": {},
  "common": {
    "view": {
      "name": "全社分析",
      "layout": [
        {
          "i": "pane1",
          "x": 0,
          "y": 0,
          "w": 30,
          "h": 4
        },
        {
          "i": "pane2",
          "x": 30,
          "y": 0,
          "w": 30,
          "h": 4
        },
        {
          "i": "pane3",
          "x": 0,
          "y": 4,
          "w": 60,
          "h": 4
        }
      ],
      "panes": [
        {
          "id": "pane1",
          "type": "table",
          "title": "会社別 売上ランキング",
          "sql": "WITH 集計 AS (\n  SELECT 会社名, COUNT(*) AS 件数, SUM(売上) AS 売上\n  FROM APP4149\n  GROUP BY 会社名\n),\n順位付き AS (\n  SELECT 会社名, 件数, 売上,\n         RANK() OVER (ORDER BY 売上 DESC) AS 順位\n  FROM 集計\n)\nSELECT 順位, 会社名, 件数, 売上\nFROM 順位付き\nWHERE 順位 <= 5\nORDER BY 順位",
          "options": {
            "density": "compact",
            "columns": {
              "順位": {
                "width": 60,
                "align": "right"
              },
              "会社名": {
                "width": 260
              },
              "件数": {
                "width": 70,
                "align": "right"
              },
              "売上": {
                "align": "right",
                "format": {
                  "kind": "number",
                  "prefix": "¥",
                  "thousandSep": true,
                  "decimals": 0
                }
              }
            },
            "rules": [
              {
                "kind": "threshold",
                "column": "順位",
                "op": "=",
                "value": 1,
                "tone": "good",
                "scope": "row"
              }
            ]
          }
        },
        {
          "id": "pane2",
          "type": "table",
          "title": "フェーズ別 上位 3 社",
          "sql": "WITH 集計 AS (\n  SELECT 商談フェーズ, 会社名, SUM(売上) AS 売上\n  FROM APP4149\n  WHERE 商談フェーズ <> ''\n  GROUP BY 商談フェーズ, 会社名\n),\n順位付き AS (\n  SELECT 商談フェーズ, 会社名, 売上,\n         RANK() OVER (PARTITION BY 商談フェーズ ORDER BY 売上 DESC) AS 順位\n  FROM 集計\n)\nSELECT 商談フェーズ, 順位, 会社名, 売上\nFROM 順位付き\nWHERE 順位 <= 3\nORDER BY 商談フェーズ, 順位",
          "options": {
            "density": "compact",
            "columns": {
              "商談フェーズ": {
                "width": 100,
                "align": "center"
              },
              "順位": {
                "width": 60,
                "align": "right"
              },
              "会社名": {
                "width": 260
              },
              "売上": {
                "align": "right",
                "format": {
                  "kind": "number",
                  "prefix": "¥",
                  "thousandSep": true,
                  "decimals": 0
                }
              }
            },
            "rules": [
              {
                "kind": "threshold",
                "column": "順位",
                "op": "=",
                "value": 1,
                "tone": "good",
                "scope": "row"
              }
            ]
          }
        },
        {
          "id": "pane3",
          "type": "table",
          "title": "案件ごとの活動量(活動履歴と結合)",
          "sql": "SELECT d.案件名, d.商談フェーズ, d.売上,\n       COUNT(*) AS 活動件数\nFROM APP4147 a\nJOIN APP4149 d ON a.案件No = d.案件No_\nGROUP BY d.案件名, d.商談フェーズ, d.売上\nORDER BY 活動件数 DESC\nLIMIT 10",
          "options": {
            "density": "compact",
            "columns": {
              "案件名": {
                "width": 320
              },
              "商談フェーズ": {
                "width": 100,
                "align": "center"
              },
              "売上": {
                "align": "right",
                "format": {
                  "kind": "number",
                  "prefix": "¥",
                  "thousandSep": true,
                  "decimals": 0
                }
              },
              "活動件数": {
                "width": 90,
                "align": "right"
              }
            }
          }
        }
      ]
    }
  },
  "deployOnSave": true
}
sfa-案件管理-サマリー-タブ.json — 共通を 1 段のまま 2 タブに(インポート)
{
  "schemaVersion": 2,
  "edition": "pro",
  "views": {},
  "common": {
    "view": {
      "name": "営業サマリー",
      "layout": [],
      "panes": [],
      "tabs": [
        {
          "id": "tab1",
          "name": "受注",
          "panes": [
            {
              "id": "pane1",
              "type": "kpi",
              "title": "受注金額(全社)",
              "sql": "SELECT SUM(売上) AS メイン値,\n       50000000 AS 目標値\nFROM APP4149\nWHERE 商談フェーズ = '受注'",
              "options": {
                "fontSize": "xlarge",
                "goalBar": true,
                "format": {
                  "prefix": "¥",
                  "thousandSep": true,
                  "decimals": 0
                },
                "rules": [
                  {
                    "target": "rate",
                    "op": ">=",
                    "value": 100,
                    "tone": "good",
                    "apply": "border"
                  },
                  {
                    "target": "rate",
                    "op": "<",
                    "value": 90,
                    "tone": "warn",
                    "apply": "border"
                  }
                ]
              }
            },
            {
              "id": "pane2",
              "type": "kpi",
              "title": "商談フェーズ別 金額(全社)",
              "sql": "SELECT 商談フェーズ AS タイトル,\n       SUM(売上)  AS メイン値\nFROM APP4149\nWHERE 商談フェーズ <> ''\nGROUP BY 商談フェーズ\nORDER BY メイン値 DESC",
              "options": {
                "multi": true,
                "maxTiles": 6,
                "format": {
                  "scale": "man",
                  "decimals": 0,
                  "thousandSep": true
                }
              }
            },
            {
              "id": "pane3",
              "type": "markdown",
              "title": "今期サマリー(全社)",
              "sql": "SELECT COUNT(*) AS 受注件数,\n       ROUND(SUM(売上) / 10000) AS 受注金額万\nFROM APP4149\nWHERE 商談フェーズ = '受注'",
              "options": {
                "markdown": "- 受注 **{{受注件数}}** 件 / **{{受注金額万}}** 万円\n- **全社の数字です**(一覧の絞り込みとは連動しません)",
                "fontSize": "normal"
              }
            }
          ],
          "layout": [
            {
              "i": "pane1",
              "x": 0,
              "y": 0,
              "w": 14,
              "h": 3
            },
            {
              "i": "pane2",
              "x": 14,
              "y": 0,
              "w": 26,
              "h": 3
            },
            {
              "i": "pane3",
              "x": 40,
              "y": 0,
              "w": 20,
              "h": 3
            }
          ]
        },
        {
          "id": "tab2",
          "name": "ランキング",
          "panes": [
            {
              "id": "pane4",
              "type": "table",
              "title": "会社別 売上ランキング",
              "sql": "WITH 集計 AS (\n  SELECT 会社名, COUNT(*) AS 件数, SUM(売上) AS 売上\n  FROM APP4149\n  GROUP BY 会社名\n),\n順位付き AS (\n  SELECT 会社名, 件数, 売上,\n         RANK() OVER (ORDER BY 売上 DESC) AS 順位\n  FROM 集計\n)\nSELECT 順位, 会社名, 件数, 売上\nFROM 順位付き\nWHERE 順位 <= 5\nORDER BY 順位",
              "options": {
                "density": "compact",
                "columns": {
                  "順位": {
                    "width": 60,
                    "align": "right"
                  },
                  "会社名": {
                    "width": 260
                  },
                  "件数": {
                    "width": 70,
                    "align": "right"
                  },
                  "売上": {
                    "align": "right",
                    "format": {
                      "kind": "number",
                      "prefix": "¥",
                      "thousandSep": true,
                      "decimals": 0
                    }
                  }
                },
                "rules": [
                  {
                    "kind": "threshold",
                    "column": "順位",
                    "op": "=",
                    "value": 1,
                    "tone": "good",
                    "scope": "row"
                  }
                ]
              }
            },
            {
              "id": "pane5",
              "type": "table",
              "title": "フェーズ別 上位 3 社",
              "sql": "WITH 集計 AS (\n  SELECT 商談フェーズ, 会社名, SUM(売上) AS 売上\n  FROM APP4149\n  WHERE 商談フェーズ <> ''\n  GROUP BY 商談フェーズ, 会社名\n),\n順位付き AS (\n  SELECT 商談フェーズ, 会社名, 売上,\n         RANK() OVER (PARTITION BY 商談フェーズ ORDER BY 売上 DESC) AS 順位\n  FROM 集計\n)\nSELECT 商談フェーズ, 順位, 会社名, 売上\nFROM 順位付き\nWHERE 順位 <= 3\nORDER BY 商談フェーズ, 順位",
              "options": {
                "density": "compact",
                "columns": {
                  "商談フェーズ": {
                    "width": 100,
                    "align": "center"
                  },
                  "順位": {
                    "width": 60,
                    "align": "right"
                  },
                  "会社名": {
                    "width": 260
                  },
                  "売上": {
                    "align": "right",
                    "format": {
                      "kind": "number",
                      "prefix": "¥",
                      "thousandSep": true,
                      "decimals": 0
                    }
                  }
                },
                "rules": [
                  {
                    "kind": "threshold",
                    "column": "順位",
                    "op": "=",
                    "value": 1,
                    "tone": "good",
                    "scope": "row"
                  }
                ]
              }
            }
          ],
          "layout": [
            {
              "i": "pane4",
              "x": 0,
              "y": 0,
              "w": 30,
              "h": 3
            },
            {
              "i": "pane5",
              "x": 30,
              "y": 0,
              "w": 30,
              "h": 3
            }
          ]
        }
      ]
    }
  }
}
sfa-案件管理-タブ.json — 9 ペインを 1 段 × 4 タブに(ビューへ取り込む)
{
  "schemaVersion": 2,
  "edition": "pro",
  "views": {},
  "common": {
    "view": {
      "name": "営業ダッシュボード",
      "layout": [],
      "panes": [],
      "tabs": [
        {
          "id": "tab1",
          "name": "サマリー",
          "panes": [
            {
              "id": "pane1",
              "type": "kpi",
              "title": "受注金額",
              "sql": "SELECT SUM(売上) AS メイン値,\n       50000000 AS 目標値\nFROM APP4149\nWHERE 商談フェーズ = '受注'",
              "options": {
                "fontSize": "xlarge",
                "goalBar": true,
                "format": {
                  "prefix": "¥",
                  "thousandSep": true,
                  "decimals": 0
                },
                "rules": [
                  {
                    "target": "rate",
                    "op": ">=",
                    "value": 100,
                    "tone": "good",
                    "apply": "border"
                  },
                  {
                    "target": "rate",
                    "op": "<",
                    "value": 90,
                    "tone": "warn",
                    "apply": "border"
                  }
                ]
              }
            },
            {
              "id": "pane2",
              "type": "kpi",
              "title": "商談フェーズ別 金額",
              "sql": "SELECT 商談フェーズ AS タイトル,\n       SUM(売上)  AS メイン値\nFROM APP4149\nWHERE 商談フェーズ <> ''\nGROUP BY 商談フェーズ\nORDER BY メイン値 DESC",
              "options": {
                "multi": true,
                "maxTiles": 6,
                "format": {
                  "scale": "man",
                  "decimals": 0,
                  "thousandSep": true
                }
              }
            },
            {
              "id": "pane3",
              "type": "markdown",
              "title": "サマリー",
              "sql": "SELECT COUNT(*) AS 受注件数,\n       ROUND(SUM(売上) / 10000) AS 受注金額万\nFROM APP4149\nWHERE 商談フェーズ = '受注'",
              "options": {
                "markdown": "### 今期サマリー\n\n- 受注 **{{受注件数}}** 件 / **{{受注金額万}}** 万円\n- 目標 5,000 万円に対する進捗は左の KPI カードを参照\n- <span style=\"color:#c0392b\">金額は税抜・受注フェーズのみ集計</span>",
                "fontSize": "normal"
              }
            }
          ],
          "layout": [
            {
              "i": "pane1",
              "x": 0,
              "y": 0,
              "w": 14,
              "h": 3
            },
            {
              "i": "pane2",
              "x": 14,
              "y": 0,
              "w": 26,
              "h": 3
            },
            {
              "i": "pane3",
              "x": 40,
              "y": 0,
              "w": 20,
              "h": 3
            }
          ]
        },
        {
          "id": "tab2",
          "name": "グラフ",
          "panes": [
            {
              "id": "pane4",
              "type": "bar",
              "title": "商談フェーズ別 件数",
              "sql": "SELECT 商談フェーズ,\n       COUNT(*) AS 件数\nFROM APP4149\nWHERE 商談フェーズ <> ''\nGROUP BY 商談フェーズ\nORDER BY 件数 DESC",
              "options": {
                "legend": "none",
                "mapping": {
                  "x": "商談フェーズ",
                  "valueColumns": [
                    "件数"
                  ]
                }
              }
            },
            {
              "id": "pane5",
              "type": "donut",
              "title": "提案商品別 売上構成比",
              "sql": "SELECT 提案商品,\n       SUM(売上) AS 売上\nFROM APP4149\nWHERE 提案商品 <> ''\nGROUP BY 提案商品\nORDER BY 売上 DESC",
              "options": {
                "legend": "right",
                "topN": 8,
                "mapping": {
                  "x": "提案商品",
                  "value": "売上"
                },
                "valueFormat": {
                  "scale": "man",
                  "decimals": 0,
                  "thousandSep": true
                }
              }
            },
            {
              "id": "pane6",
              "type": "combo",
              "title": "月次 売上と件数",
              "sql": "SELECT DATE_FORMAT(受注予定日, '%Y-%m') AS 年月,\n       SUM(売上) AS 売上,\n       COUNT(*)  AS 件数\nFROM APP4149\nWHERE 受注予定日 <> ''\nGROUP BY DATE_FORMAT(受注予定日, '%Y-%m')\nORDER BY 年月",
              "options": {
                "legend": "top",
                "mapping": {
                  "x": "年月"
                },
                "bars": [
                  "売上"
                ],
                "lines": [
                  "件数"
                ],
                "valueFormat": {
                  "scale": "man",
                  "decimals": 0,
                  "thousandSep": true
                }
              }
            }
          ],
          "layout": [
            {
              "i": "pane4",
              "x": 0,
              "y": 0,
              "w": 20,
              "h": 5
            },
            {
              "i": "pane5",
              "x": 20,
              "y": 0,
              "w": 20,
              "h": 5
            },
            {
              "i": "pane6",
              "x": 40,
              "y": 0,
              "w": 20,
              "h": 5
            }
          ]
        },
        {
          "id": "tab3",
          "name": "集計",
          "panes": [
            {
              "id": "pane7",
              "type": "barH",
              "title": "業種別 売上(顧客管理と結合)",
              "sql": "SELECT c.業種,\n       SUM(d.売上) AS 売上\nFROM APP4149 d\nJOIN APP4148 c ON d.顧客No_ = c.顧客No\nGROUP BY c.業種\nORDER BY 売上 DESC",
              "options": {
                "legend": "none",
                "mapping": {
                  "x": "業種",
                  "valueColumns": [
                    "売上"
                  ]
                },
                "valueFormat": {
                  "scale": "man",
                  "decimals": 0,
                  "thousandSep": true
                }
              }
            },
            {
              "id": "pane8",
              "type": "table",
              "title": "顧客ランク別 実績(顧客管理と結合)",
              "sql": "SELECT c.顧客ランク AS ランク,\n       COUNT(*)     AS 件数,\n       SUM(d.売上)  AS 売上\nFROM APP4149 d\nJOIN APP4148 c ON d.顧客No_ = c.顧客No\nGROUP BY c.顧客ランク\nORDER BY c.顧客ランク",
              "options": {
                "density": "compact",
                "columns": {
                  "ランク": {
                    "width": 80,
                    "align": "center"
                  },
                  "件数": {
                    "width": 90,
                    "align": "right"
                  },
                  "売上": {
                    "align": "right",
                    "format": {
                      "kind": "number",
                      "prefix": "¥",
                      "thousandSep": true,
                      "decimals": 0
                    }
                  }
                },
                "totals": {
                  "件数": "sum",
                  "売上": "sum"
                },
                "rules": [
                  {
                    "kind": "heatmap",
                    "column": "売上"
                  }
                ]
              }
            }
          ],
          "layout": [
            {
              "i": "pane7",
              "x": 0,
              "y": 0,
              "w": 30,
              "h": 5
            },
            {
              "i": "pane8",
              "x": 30,
              "y": 0,
              "w": 30,
              "h": 5
            }
          ]
        },
        {
          "id": "tab4",
          "name": "明細",
          "panes": [
            {
              "id": "pane9",
              "type": "table",
              "title": "案件明細(金額順)",
              "sql": "SELECT $id, 案件名, 会社名, 商談フェーズ, 確度, 売上, 受注予定日\nFROM APP4149\nWHERE 商談フェーズ <> ''\nORDER BY 売上 DESC\nLIMIT 100",
              "options": {
                "density": "compact",
                "columns": {
                  "$id": {
                    "hidden": true
                  },
                  "案件名": {
                    "width": 300
                  },
                  "会社名": {
                    "width": 240
                  },
                  "商談フェーズ": {
                    "width": 110,
                    "align": "center"
                  },
                  "確度": {
                    "width": 80,
                    "align": "center"
                  },
                  "売上": {
                    "align": "right",
                    "format": {
                      "kind": "number",
                      "prefix": "¥",
                      "thousandSep": true,
                      "decimals": 0
                    }
                  },
                  "受注予定日": {
                    "width": 120,
                    "align": "center",
                    "format": {
                      "kind": "date",
                      "pattern": "YYYY/MM/DD"
                    }
                  }
                },
                "totals": {
                  "売上": "sum"
                },
                "rules": [
                  {
                    "kind": "threshold",
                    "column": "売上",
                    "op": ">=",
                    "value": 6000000,
                    "tone": "good"
                  },
                  {
                    "kind": "threshold",
                    "column": "商談フェーズ",
                    "op": "=",
                    "value": "内示",
                    "tone": "warn",
                    "scope": "row"
                  }
                ],
                "initialSort": [
                  {
                    "column": "売上",
                    "desc": true
                  }
                ]
              }
            }
          ],
          "layout": [
            {
              "i": "pane9",
              "x": 0,
              "y": 0,
              "w": 60,
              "h": 6
            }
          ]
        }
      ]
    }
  }
}
sfa-案件管理-グラフ8種.json — グラフ 8 種の実例を 2 タブで(ビューへ取り込む)
{
  "schemaVersion": 2,
  "edition": "pro",
  "views": {},
  "common": {
    "view": {
      "name": "グラフ 8 種",
      "layout": [],
      "panes": [],
      "tabs": [
        {
          "id": "tab1",
          "name": "棒・複合",
          "panes": [
            {
              "id": "pane1",
              "type": "bar",
              "title": "商談フェーズ別 件数",
              "sql": "SELECT 商談フェーズ, COUNT(*) AS 件数\nFROM APP4149\nWHERE 商談フェーズ <> ''\nGROUP BY 商談フェーズ\nORDER BY 件数 DESC",
              "options": {
                "legend": "none",
                "valueLabels": true,
                "showTotal": true,
                "xLabelsAll": true,
                "valueFormat": {
                  "suffix": " 件"
                }
              }
            },
            {
              "id": "pane2",
              "type": "barH",
              "title": "業種別 売上(顧客管理と結合)",
              "sql": "SELECT c.業種, SUM(d.売上) AS 売上\nFROM APP4149 d\nJOIN APP4148 c ON d.顧客No_ = c.顧客No\nGROUP BY c.業種\nORDER BY 売上 DESC",
              "options": {
                "legend": "none",
                "valueLabels": true,
                "showTotal": true,
                "unitOnAxis": true,
                "valueFormat": {
                  "scale": "million",
                  "decimals": 1,
                  "hideScaleSuffix": true
                }
              }
            },
            {
              "id": "pane3",
              "type": "barStacked",
              "title": "商談フェーズ × 提案商品 の売上",
              "sql": "SELECT 商談フェーズ, 提案商品, SUM(売上) AS 売上\nFROM APP4149\nWHERE 商談フェーズ <> '' AND 提案商品 <> ''\nGROUP BY 商談フェーズ, 提案商品\nORDER BY 商談フェーズ",
              "options": {
                "legend": "bottom",
                "valueFormat": {
                  "scale": "man",
                  "decimals": 0
                }
              }
            },
            {
              "id": "pane4",
              "type": "combo",
              "title": "月次 売上と件数",
              "sql": "SELECT DATE_FORMAT(受注予定日, '%Y-%m') AS 年月, SUM(売上) AS 売上, COUNT(*) AS 件数\nFROM APP4149\nWHERE 受注予定日 <> ''\nGROUP BY DATE_FORMAT(受注予定日, '%Y-%m')\nORDER BY 年月",
              "options": {
                "legend": "bottom",
                "bars": [
                  "売上"
                ],
                "lines": [
                  "件数"
                ],
                "valueFormat": {
                  "scale": "man",
                  "decimals": 0
                },
                "valueFormatRight": {
                  "suffix": " 件"
                },
                "refLines": [
                  {
                    "value": 10000000,
                    "label": "月次目標"
                  }
                ]
              }
            }
          ],
          "layout": [
            {
              "i": "pane1",
              "x": 0,
              "y": 0,
              "w": 30,
              "h": 5
            },
            {
              "i": "pane2",
              "x": 30,
              "y": 0,
              "w": 30,
              "h": 5
            },
            {
              "i": "pane3",
              "x": 0,
              "y": 5,
              "w": 30,
              "h": 5
            },
            {
              "i": "pane4",
              "x": 30,
              "y": 5,
              "w": 30,
              "h": 5
            }
          ]
        },
        {
          "id": "tab2",
          "name": "線・円",
          "panes": [
            {
              "id": "pane5",
              "type": "line",
              "title": "月次 件数(フェーズ別・欠損あり)",
              "sql": "SELECT DATE_FORMAT(受注予定日, '%Y-%m') AS 年月, 商談フェーズ, COUNT(*) AS 件数\nFROM APP4149\nWHERE 受注予定日 <> '' AND 商談フェーズ <> ''\nGROUP BY DATE_FORMAT(受注予定日, '%Y-%m'), 商談フェーズ\nORDER BY 年月",
              "options": {
                "legend": "bottom",
                "gap": "break",
                "valueFormat": {
                  "suffix": " 件"
                }
              }
            },
            {
              "id": "pane6",
              "type": "area",
              "title": "月次 売上(商品別・積み上げ)",
              "sql": "SELECT DATE_FORMAT(受注予定日, '%Y-%m') AS 年月, 提案商品, SUM(売上) AS 売上\nFROM APP4149\nWHERE 受注予定日 <> '' AND 提案商品 <> ''\nGROUP BY DATE_FORMAT(受注予定日, '%Y-%m'), 提案商品\nORDER BY 年月",
              "options": {
                "legend": "bottom",
                "gap": "zero",
                "valueFormat": {
                  "scale": "man",
                  "decimals": 0
                }
              }
            },
            {
              "id": "pane7",
              "type": "pie",
              "title": "業種別 案件数",
              "sql": "SELECT c.業種, COUNT(*) AS 件数\nFROM APP4149 d\nJOIN APP4148 c ON d.顧客No_ = c.顧客No\nGROUP BY c.業種\nORDER BY 件数 DESC",
              "options": {
                "legend": "right",
                "pieLabels": "percent",
                "valueFormat": {
                  "suffix": " 件"
                }
              }
            },
            {
              "id": "pane8",
              "type": "donut",
              "title": "提案商品別 売上構成比",
              "sql": "SELECT 提案商品, SUM(売上) AS 売上\nFROM APP4149\nWHERE 提案商品 <> ''\nGROUP BY 提案商品\nORDER BY 売上 DESC",
              "options": {
                "legend": "right",
                "pieLabels": "both",
                "showTotal": true,
                "valueFormat": {
                  "scale": "man",
                  "decimals": 0
                }
              }
            }
          ],
          "layout": [
            {
              "i": "pane5",
              "x": 0,
              "y": 0,
              "w": 30,
              "h": 5
            },
            {
              "i": "pane6",
              "x": 30,
              "y": 0,
              "w": 30,
              "h": 5
            },
            {
              "i": "pane7",
              "x": 0,
              "y": 5,
              "w": 30,
              "h": 5
            },
            {
              "i": "pane8",
              "x": 30,
              "y": 5,
              "w": 30,
              "h": 5
            }
          ]
        }
      ]
    }
  }
}
shift-sample-dates.mjs — サンプルデータの日付を今日基準へ振り直す(相対日付を試す場合のみ)
#!/usr/bin/env node
/**
 * SFA パックのサンプルデータの日付を「今日」から見た位置へ振り直す。
 *
 * アプリストアの SFA パックは、受注予定日などが **2025 年で固定**されています。
 * そのため「今月の受注予定」「今週の活動予定」といった相対日付の一覧・ペインが
 * すべて 0 件になり、kSQL の相対日付を試せません。
 *
 * このスクリプトは**レコードを増やしません。既にある日付をずらすだけ**です。
 * 件数が変わらないので、日付に依存しない集計(商談フェーズ別の合計など)は
 * 実行前とまったく同じ値になります。
 *
 *   node shift-sample-dates.mjs              下見(何も書き換えない)
 *   node shift-sample-dates.mjs --apply      実際に書き換える
 *
 * 環境変数:
 *   KINTONE_BASE_URL   https://xxxxx.cybozu.com
 *   KINTONE_USERNAME / KINTONE_PASSWORD   または   KINTONE_API_TOKEN
 *
 * 引数:
 *   --deal-app 4149       案件管理のアプリ番号
 *   --activity-app 4147   活動履歴のアプリ番号
 *   --base 2026-07-29     基準日(既定: 今日)
 *   --apply               書き換えを実行する
 *
 * 何度実行しても同じ結果になります(基準日から計算し直すため)。
 * 撮影や検証の直前に流し直してください。
 */

const args = process.argv.slice(2);
const flag = (name) => args.includes(`--${name}`);
const value = (name, fallback) => {
  const i = args.indexOf(`--${name}`);
  return i >= 0 && args[i + 1] ? args[i + 1] : fallback;
};

const DEAL_APP = value("deal-app", "4149");
const ACT_APP = value("activity-app", "4147");
const APPLY = flag("apply");

/* ---- 日付の計算 ------------------------------------------------------------
 * 月を足す計算は「同じ日を保ち、月末を超えたらその月の末日へ丸める」。
 * 1/31 に 1 か月足して 2/31 のような日付を作らないためです。 */

const iso = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;

function parseDate(s) {
  const [y, m, d] = s.split("-").map(Number);
  return new Date(y, m - 1, d);
}

function addDays(base, days) {
  const d = new Date(base);
  d.setDate(d.getDate() + days);
  return d;
}

function addMonthsKeepingDay(base, months, day) {
  const y = base.getFullYear();
  const m = base.getMonth() + months;
  const lastDay = new Date(y, m + 1, 0).getDate();
  return new Date(y, m, Math.min(day, lastDay));
}

const BASE = parseDate(value("base", iso(new Date())));

/* ---- 割り当て --------------------------------------------------------------
 * 「どの区間に何件入るか」をここで決めています。読み替えて調整できます。 */

/** 受注予定日: 基準日の月からのずれ(月数)。元の日付が古い順に当てる。
 *  先月 3 件・今月 4 件・翌月 3 件を必ず作り、残りを今年の前半へ散らす。 */
const DEAL_MONTH_OFFSETS = [-6, -5, -4, -3, -2, -1, -1, -1, 0, 0, 0, 0, 1, 1, 1, 2];

/** 次回アクション日: 基準日からのずれ(日数)。
 *  マイナス = 期限超過(「活動漏れ」に出る)、0 = 今日、1 = 明日。 */
const ACTION_DAY_OFFSETS = [-14, -7, -3, -1, 0, 1, 2, 10, 14, 20, 35, 40, 50, 60];

/** 対応日付(活動履歴): 基準日からのずれ(日数)。過去 3 か月に散らす。 */
const ACTIVITY_DAY_OFFSETS = [-88, -80, -73, -66, -59, -52, -45, -38, -31, -24, -19, -14, -10, -7, -4, -2, -1, 0];

/* ---- kintone REST ---------------------------------------------------------- */

const BASE_URL = (process.env.KINTONE_BASE_URL ?? "").replace(/\/$/, "");
const TOKEN = process.env.KINTONE_API_TOKEN ?? "";
const USER = process.env.KINTONE_USERNAME ?? "";
const PASS = process.env.KINTONE_PASSWORD ?? "";

if (!BASE_URL || (!TOKEN && !(USER && PASS))) {
  console.error("KINTONE_BASE_URL と、KINTONE_API_TOKEN または KINTONE_USERNAME/KINTONE_PASSWORD を設定してください。");
  process.exit(1);
}

function authHeaders(withBody) {
  const h = TOKEN
    ? { "X-Cybozu-API-Token": TOKEN }
    : { "X-Cybozu-Authorization": Buffer.from(`${USER}:${PASS}`).toString("base64") };
  // GET に Content-Type を付けると kintone が CB_IL02 で弾く
  return withBody ? { ...h, "Content-Type": "application/json" } : h;
}

async function call(method, path, body) {
  const res = await fetch(`${BASE_URL}${path}`, {
    method,
    headers: authHeaders(body !== undefined),
    ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
  });
  const json = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error(`${method} ${path}${res.status} ${JSON.stringify(json)}`);
  return json;
}

/** 全件取得(このサンプルは数十件なので 1 回で足りるが、念のため繰り返す) */
async function fetchAll(app, fields) {
  const out = [];
  let id = 0;
  for (;;) {
    const q = `$id > ${id} order by $id asc limit 500`;
    const params = new URLSearchParams({ app, query: q });
    for (const f of fields) params.append("fields", f);
    const { records } = await call("GET", `/k/v1/records.json?${params}`);
    if (!records.length) return out;
    out.push(...records);
    id = Number(records[records.length - 1].$id.value);
  }
}

async function put(app, records) {
  for (let i = 0; i < records.length; i += 100) {
    await call("PUT", "/k/v1/records.json", { app, records: records.slice(i, i + 100) });
  }
}

/* ---- 本体 ------------------------------------------------------------------ */

/** 案件管理: 受注予定日を月単位でずらし、初回商談日は同じ日数だけ動かして関係を保つ。
 *  次回アクション日は「活動漏れ」「今週の活動予定」を作るため個別に当てる。 */
async function shiftDeals() {
  const records = await fetchAll(DEAL_APP, ["$id", "受注予定日", "初回商談日", "次回アクション日"]);
  const dated = records.filter((r) => r.受注予定日.value);
  dated.sort((a, b) => a.受注予定日.value.localeCompare(b.受注予定日.value));

  const updates = [];
  const rows = [];

  dated.forEach((r, i) => {
    const before = parseDate(r.受注予定日.value);
    const offset = DEAL_MONTH_OFFSETS[Math.min(i, DEAL_MONTH_OFFSETS.length - 1)];
    const after = addMonthsKeepingDay(BASE, offset, before.getDate());
    const record = { 受注予定日: { value: iso(after) } };

    updates.push({ id: r.$id.value, record });
    rows.push([`案件 #${r.$id.value}`, "受注予定日", r.受注予定日.value, iso(after)]);

    // 初回商談日は受注予定日と同じ日数だけ動かす(商談 → 受注の間隔を保つ)
    if (r.初回商談日?.value) {
      const moved = Math.round((after - before) / 86400000);
      const shifted = iso(addDays(parseDate(r.初回商談日.value), moved));
      record.初回商談日 = { value: shifted };
      rows.push([`案件 #${r.$id.value}`, "初回商談日", r.初回商談日.value, shifted]);
    }
  });

  // 次回アクション日は日単位で当てる(今週・期限超過を必ず作る)
  const withAction = records.filter((r) => r.次回アクション日.value);
  withAction.sort((a, b) => a.次回アクション日.value.localeCompare(b.次回アクション日.value));
  withAction.forEach((r, i) => {
    const after = addDays(BASE, ACTION_DAY_OFFSETS[Math.min(i, ACTION_DAY_OFFSETS.length - 1)]);
    const found = updates.find((u) => u.id === r.$id.value);
    if (found) found.record.次回アクション日 = { value: iso(after) };
    else updates.push({ id: r.$id.value, record: { 次回アクション日: { value: iso(after) } } });
    rows.push([`案件 #${r.$id.value}`, "次回アクション日", r.次回アクション日.value, iso(after)]);
  });

  return { updates, rows, skipped: records.length - dated.length };
}

/** 活動履歴: 対応日付を過去 3 か月へ散らす(今日・昨日を含む) */
async function shiftActivities() {
  const records = await fetchAll(ACT_APP, ["$id", "対応日付"]);
  const dated = records.filter((r) => r.対応日付.value);
  dated.sort((a, b) => a.対応日付.value.localeCompare(b.対応日付.value));

  const updates = [];
  const rows = [];
  dated.forEach((r, i) => {
    const after = addDays(BASE, ACTIVITY_DAY_OFFSETS[Math.min(i, ACTIVITY_DAY_OFFSETS.length - 1)]);
    updates.push({ id: r.$id.value, record: { 対応日付: { value: iso(after) } } });
    rows.push([`活動 #${r.$id.value}`, "対応日付", r.対応日付.value, iso(after)]);
  });
  return { updates, rows, skipped: records.length - dated.length };
}

function printTable(rows) {
  const w = [0, 0, 0].map((_, i) => Math.max(...rows.map((r) => [...r[i]].length)));
  for (const r of rows) {
    console.log(`  ${r[0].padEnd(w[0])}  ${r[1].padEnd(w[1])}  ${r[2].padEnd(w[2])}${r[3]}`);
  }
}

const deals = await shiftDeals();
const acts = await shiftActivities();

console.log(`基準日: ${iso(BASE)}`);
console.log(`\n案件管理 APP${DEAL_APP}(${deals.updates.length} 件を更新 / 日付なし ${deals.skipped} 件は対象外)`);
printTable(deals.rows);
console.log(`\n活動履歴 APP${ACT_APP}(${acts.updates.length} 件を更新)`);
printTable(acts.rows);

if (!APPLY) {
  console.log("\n下見だけで終了しました。実際に書き換えるには --apply を付けてください。");
  process.exit(0);
}

await put(DEAL_APP, deals.updates);
await put(ACT_APP, acts.updates);
console.log("\n書き換えました。");

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?