kintone の一覧に数字を 1 つ大きく出す。それだけなら難しくありません。難しいのは、その数字を見た人が次の行動を決められる形にすることです。「今月 810 万円」だけでは、それが good なのか bad なのか分かりません。先月と比べてどうか、目標に対してどこまで来たかが添えられて初めて意味を持ちます。
kSQL Dashboard Pro の KPI カードは、そのための道具立てを持っています。この記事では SQL・ペイン設定・表示結果を 1 セットにして、8 種類の KPI カードを作ります。SQL はすべてコピペで動きます。
kSQL Dashboard Pro デモ(全 5 回)
⓪ 準備編 / ① KPI カード(この記事) / ② データ表 / ③ グラフ / ④ マークダウン**アプリの用意は ⓪ 準備編**にまとめてあります(5 本とも同じアプリを使います)。
上はこの連載で作るダッシュボードの全体です。この記事はこのうち KPI カードの部分を、最小の形から順に作っていきます。
前提(この記事で使うバージョン・アプリ)
バージョンの前提はここだけです。以降の本文では省略します。
| 項目 | 本記事の前提 |
|---|---|
| プラグイン | kSQL Dashboard Pro Ver.1 |
| SQL エンジン | kintone-sql-tools 3.66.1 系 |
| kintone | 公式対応ブラウザの最新版(PC)。モバイルは対象外 |
| 対象アプリ | この記事用に作る 売上明細(約 1,500 件)と 月次目標(288 件) |
アプリ番号は環境ごとに変わります。 SQL 中の
APP4239などの数字は、ご自身の環境の番号(アプリの URL の/k/4239/の部分)に読み替えてください。
設定 JSON の取り込みでは書き換え不要です。 取り込み時の確認ダイアログ「アプリ番号の変換」に変換先を入力すると、SQL 中の番号を自動で置き換えて取り込めます。
アプリの関係
3 つのアプリを使います。この記事で使うのは売上明細と月次目標の 2 つで、商品分類マスタは ② データ表の階層展開で登場します。
kintone のアプリ間に外部キーはありません。 上の線は「SQL で突き合わせるときのキー」です。ルックアップも関連レコードも使わず、SQL の条件だけで結び付けます。
ここで押さえておきたいのは 担当者名(文字列)と 担当者user(ユーザー選択)を別々に持っていることです。理由は例 5 で説明します。
アプリを用意する
**⓪ 準備編にまとめてあります。**アプリテンプレートから 3 アプリを作り、ブラウザーの開発ツールでデータを入れて、設定 JSON を取り込む — 手順は 3 つ、15 分ほどです。インストールするものはありません。
②③④ も同じアプリを使うので、一度作れば 4 本とも動きます。
KPI カードが返すべき列の形
表示タイプごとに「返すべき列の形」が決まっています。ここがずれると、SQL は通るのに描画だけ壊れます。KPI カードの決まりはこれだけです。
結果は 1 行。列の名前で役割が決まる。
2 行以上返しても 1 行目しか使いません(タイル表示だけは例外。後述)。
最小 — 3 行で出る
SELECT COUNT(*) FROM APP4239
ペインの表示タイプは「KPI カード」を選びます(設定画面の表示タイプ欄)。SQL はこれだけです。
役割を示す列名がないときは 1 列目がメイン値になります。AS メイン値 を書く必要はありません。
予約列名 — 名前を付けると役割が付く
列に決まった名前を付けると、カードがその役割で扱います。大文字小文字は区別しません。
| 役割 | 予約列名 | 効果 |
|---|---|---|
| メイン値 |
メイン値 / value / main
|
巨大表示。未解決なら 1 列目 |
| タイトル |
タイトル / title
|
カード見出し(ペインの設定が優先) |
| 比較値 |
比較値 / delta / comparison
|
そのまま増減として表示 |
| 基準値 |
基準値 / 前月値 / base / previous
|
メイン値との増減率を自動計算 |
| 目標値 |
目標値 / target / goal
|
達成率を自動計算 |
| 達成率 |
達成率 / rate / achievement
|
サブ情報として表示 |
役割の決まり方には順番があります。
- 列の割り当て(設定画面で列名を直接指定)
- 予約列名
- 1 列目フォールバック
つまり、SQL の別名を予約列名にしておけば設定は要りません。設定で明示したいときだけ 1 を使います。
比較値と基準値は排他です。 比較値があると「そのまま表示」になり、自動計算の % は出ません。基準値だと % だけが出て、元の金額は出ません。同じ枠に両方は出せません。
例 1: 今月の売上 — 相対日付で絞る
SELECT SUM(金額) AS メイン値
FROM APP4239
WHERE 売上日 = THIS_MONTH() AND 売上ステータス = '確定'
表示タブでフォーマットを指定します。
| 設定 | 値 |
|---|---|
| 表示スケール | 万 |
| 単位(接尾) | 円 |
| 小数桁 | 0 |
THIS_MONTH() は相対日付関数です。単に便利なだけではありません。
この条件は kintone のサーバー側へ渡され、該当分だけが返ってきます。 書かない場合は全件(1,500 件)を取り寄せてからブラウザで捨てることになります。件数が増えると通信量が増えるだけでなく、取得上限(既定 10,000 件)を超えた時点で集計はエラーになります。
「動いていたのに、データが増えたら突然エラーになった」の多くはこれです。相対日付で絞る書き方を選ぶ = 将来もエラーにならない書き方を選ぶ、と考えてよいです。
売上ステータス = '確定' も同じくサーバー側へ渡ります。この記事の数字はすべて「確定」のみで、保留・取消は含みません。
例 2: 前月比を 1 クエリで
前月比を出すのに、クエリを 2 本走らせる必要はありません。今月と先月をまとめて取ってきて、列の振り分けだけ SQL でやります。
SELECT
SUM(CASE WHEN YEAR(売上日) * 12 + MONTH(売上日) = YEAR(CURRENT_DATE()) * 12 + MONTH(CURRENT_DATE())
THEN 金額 ELSE 0 END) AS メイン値,
SUM(CASE WHEN YEAR(売上日) * 12 + MONTH(売上日) = YEAR(CURRENT_DATE()) * 12 + MONTH(CURRENT_DATE()) - 1
THEN 金額 ELSE 0 END) AS 前月値
FROM APP4239
WHERE (売上日 = THIS_MONTH() OR 売上日 = LAST_MONTH()) AND 売上ステータス = '確定'
要点は 2 つです。
① WHERE は OR で 2 か月ぶんを取る。 THIS_MONTH() OR LAST_MONTH() はどちらもサーバー側へ渡せるので、2 か月ぶんだけが返ってきます。
② 月の判定は YEAR() * 12 + MONTH() で行う。 単純に MONTH() だけで比べると、1 月と 12 月をまたぐときに壊れます。年に 12 を掛けて月を足した「通し番号」にすれば、年をまたいでも - 1 が正しく前月になります。
前月値(基準値)を返すと、カードが増減率を自動計算して ▼ 76.3% と出します。
月の途中なので今月は少なく、前月比は大きなマイナスになります。これは実際の運用でも毎月起きることなので、隠さずそのまま出しています。
前月の金額そのものも出したい
前述のとおり、増減の枠には % か金額のどちらかしか出せません。両方見せたいときは、タイトルを SQL 側で作ります。
SELECT
CONCAT('今月の売上(前月 ',
FORMAT(ROUND(SUM(CASE WHEN YEAR(売上日) * 12 + MONTH(売上日) = YEAR(CURRENT_DATE()) * 12 + MONTH(CURRENT_DATE()) - 1
THEN 金額 ELSE 0 END) / 10000), '#,##0'),
' 万円)') AS タイトル,
SUM(...) AS メイン値,
SUM(...) AS 前月値
FROM APP4239
WHERE (売上日 = THIS_MONTH() OR 売上日 = LAST_MONTH()) AND 売上ステータス = '確定'
FORMAT(値, '#,##0') で桁区切りが入ります。ペインの「タイトル」を空にしておくのを忘れずに — 設定側のタイトルが優先されるためです。
例 3: 目標達成率とプログレスバー
目標は別アプリ(月次目標)にあります。SET で先に合計を取ってから、本体の SELECT で使います。
SET @goal = (SELECT SUM(目標金額) FROM APP4240 WHERE 年月 = DATE_FORMAT(CURRENT_DATE(), '%Y-%m'));
SELECT SUM(金額) AS メイン値, @goal AS 目標値
FROM APP4239
WHERE 売上日 = THIS_MONTH() AND 売上ステータス = '確定'
表示タブで「達成率をプログレスバーで表示」をオンにします。
; で区切った複文がペインでそのまま書けます。描画されるのは最後に結果セットを返した文で、SET は結果を作りません。
目標値 を返すとカードが達成率を自動計算します。自分で割り算を書く必要はありません。
JOIN でも書けますが、この用途では
SETのほうが素直です。 JOIN にすると明細 1 行ごとに目標行が付くため、目標の合計を取るのにMAX()を挟むなどの工夫が要ります。「全体の目標額」という 1 つの値がほしいだけなら、先に変数へ入れてしまうほうが読めます。
例 4: 条件付きカラー — 閾値で色を変える
例 3 とまったく同じ SQLに、色のルールを足します。
| target | op | value | tone | apply |
|---|---|---|---|---|
| 達成率 | < | 90 | 注意 | 枠 |
| 達成率 | < | 70 | 重大 | 枠 |
| 達成率 | >= | 100 | 良好 | 背景 |
- 判定対象はメイン値か達成率
- 上から順に評価し、同じ適用先(文字・背景・枠)は後のルールが優先します
- ルールは 10 個まで
79% なので「注意」の枠が付いています。100% を超えると背景が変わります。
色だけに意味を持たせないほうが安全です。この例では枠と背景で見分けが付きますが、達成率の数字自体もバーの右に出ているので、色が分からない環境でも読めます。
例 5: 自分の担当分だけ表示する
LOGINUSER() を使うと、閲覧者ごとに内容が変わるカードになります。共通のダッシュボードに置いても、見る人それぞれの数字が出ます。
SELECT SUM(金額) AS メイン値
FROM APP4239
WHERE 担当者user in (LOGINUSER()) AND 売上日 = THIS_MONTH() AND 売上ステータス = '確定'
書き方に決まりがあります。
-
=は使えません。 必ずin/not inを使います - 対象は 作成者 / 更新者 / ユーザー選択フィールドだけです。文字列の氏名フィールドには使えません
-
in (LOGINUSER(), 'taro')のように他の値と混ぜられません
担当者userを別に持っているのはこのためです。 このアプリには氏名の文字列担当者名もありますが、LOGINUSER()はユーザー選択にしか効きません。逆に、集計やランキング、別アプリとの突き合わせは文字列のほうが素直に書けます。役割の違う 2 列を持つのは、実務でもよくある形です。
組織で絞る場合の注意
同じ発想で PRIMARY_ORGANIZATION() を使うと、自分の組織のデータだけを出せます。ただし落とし穴があります。
優先組織が設定されていない利用者では、この条件が無視され、全レコードが返ります。
kintone の仕様で、エンジンからもプラグインからも「条件が無視された」ことを判別できません。つまり 「自組織の実績」のつもりの数字が、その人には全社の数字として見えます 。
kSQL Dashboard Pro は一覧画面で、優先組織が無い利用者にだけそのペインへ警告を出します。それでも、書く側でも次のどちらかを取ってください。
- ペインのタイトルに前提を書く —
自組織の売上(優先組織の設定が前提) - 配布前に、対象の利用者に優先組織が設定されていることを確認する
例 6: 複数行タイル — 1 枚に複数の数字を並べる
**表示タブの「複数行をタイル表示」**をオンにすると、結果の各行がミニタイルで並びます。
SELECT 部署 AS タイトル, SUM(金額) AS メイン値
FROM APP4239
WHERE 売上日 = THIS_MONTH() AND 売上ステータス = '確定'
GROUP BY 部署
ORDER BY メイン値 DESC
予約列名の無い 2 列の結果は「1 列目 = ラベル、2 列目 = 値」と自動解釈されるので、AS タイトル / AS メイン値 は省略もできます。明示したほうが読み手には親切です。
- 条件付きカラーはタイル単位で評価されます
- 最大タイル数は既定 8。超えた分は「他 N 件」にまとまります
- タイルの並びはペイン幅で折り返します。窓の広さで列数が変わります
「他 N 件」ではなく、金額付きの「その他」を出す
maxTiles で畳むと 他 4 件 と件数しか出ません。まとめた分がいくらなのか読めません。
金額を持った「その他」がほしいなら、SQL 側で作ります。
CREATE TEMP TABLE #r AS
SELECT 商品カテゴリ, SUM(金額) AS 売上
FROM APP4239
WHERE 売上日 = THIS_YEAR() AND 売上ステータス = '確定'
GROUP BY 商品カテゴリ;
CREATE TEMP TABLE #t AS SELECT 商品カテゴリ, 売上 FROM #r ORDER BY 売上 DESC LIMIT 7;
SET @other = (SELECT SUM(売上) FROM #r WHERE 商品カテゴリ NOT IN (SELECT 商品カテゴリ FROM #t));
SELECT 商品カテゴリ AS タイトル, 売上 AS メイン値 FROM #t
UNION ALL SELECT 'その他' AS タイトル, @other AS メイン値 FROM #t LIMIT 1;
一時テーブルを 2 つ作り、上位 7 に入らなかった分を @other に集めて UNION ALL で 1 行足しています。12 分類が 8 タイルに収まり、まとめた 5 分類の合計も読めます。
金額の桁を揃えたいので、値の寄せを「右」 にしてあります(表示タブ)。
一時テーブルは閲覧者のブラウザのメモリに載ります。 1 表あたり既定 10,000 行、寿命はそのペインの 1 回の実行内だけです。ペインをまたいだ共有はできません。
つまずきポイント
実際に書いていて踏んだものだけを挙げます。
2 行返しても 1 行目しか出ない
KPI カードは単一の数字を出す型です。GROUP BY した結果をそのまま渡すと、1 行目だけが表示されて他は無視されます。複数の数字を並べたいならタイル表示(例 6)、表として読ませたいなら表タイプを使ってください。
AS の英字が小文字になる
別名に英数字が含まれると、結果の列名は小文字化されます(AS ランクA → ランクa)。見出しの表示は書いたとおりに出ますが、設定側で列名を参照するとき(列の割り当てなど)は小文字のほうを書きます。
日本語だけの別名なら、そのままの名前になります。
WHERE を省くと取得上限に当たる
例 6 の 1 本目のように GROUP BY だけ書いて期間を絞らないと、全件を取り寄せます。件数の多いアプリでは取得上限(既定 10,000 件)でエラーになります。相対日付で必ず絞ってください。
存在しない関数を書いてしまう
月を YYYY-MM にする関数は DATE_FORMAT(日付, '%Y-%m') です。FORMAT_DATE ではありません(こちらは存在せず ParseError になります)。似た名前の FORMAT(値, '#,##0') は数値の書式で、別物です。
タイトルが SQL のとおりに出ない
ペインの「タイトル」設定が優先されます。SQL の タイトル 列を使いたいときは、設定側のタイトルを空にしてください。
まとめ
KPI カードで押さえるのは 3 つだけです。
- 結果は 1 行。列名で役割が決まる — 名前を付けなければ 1 列目がメイン値
- 基準値を返せば増減率、目標値を返せば達成率が自動で出る — 割り算を自分で書かない
- 相対日付で絞る — 速度の話ではなく、そのペインが将来も動くかどうかの話
そのうえで、条件付きカラーで「見るべきカード」を目立たせ、タイル表示で 1 枚にまとめる。ここまでで、一覧の上に置く数字はだいたい作れます。
関連記事
- ⓪ 準備編 — デモアプリを 15 分で用意する
- ② データ表 — 明細も集計もこれ 1 つ
- ③ グラフ — 9 種あるが、SQL の形は 4 通り
- ④ マークダウン — 数字を、文章に埋める
- kSQL Dashboard Pro 製品紹介 — 表示タイプ・設定画面・テーマなど機能全般
- SFA パック編 / 在庫管理パック編 — 業務アプリに実際に組んだ例
- rex0220 kSQL 言語リファレンス — SQL の文法。プラグインで使えるのは読み取り系だけです
付録 — 配布ファイル
GitHub に一式を置いています
アプリテンプレート・データ投入スクリプト・設定 JSON をまとめてあります。
https://github.com/rex0220/ksql-dashboard-pro-demo
**使い方は ⓪ 準備編にまとめてあります。**設定はまとめて 1 回で取り込めます(
settings/型別-全一覧-見本.jsonに 5 一覧・43 ペイン)。この記事のぶんだけでよければ、下の JSON を使ってください。
設定 JSON(この記事の 8 ペイン)
GitHub の settings/型別-KPI-見本.json と同じものです。コピーして使えるよう全文を貼ります。
- 折りたたみを開き、コードブロック右上のコピーボタンでコピー
- テキストエディター(メモ帳等)に貼り付け、
型別-KPI-見本.jsonとして保存(文字コード UTF-8) - 設定画面の ツール → インポート でそのファイルを選択
SQL 中の
APP4239/APP4240は、取り込み時の確認ダイアログ「アプリ番号の変換」でご自身の環境の番号へ変換できます(JSON を手で書き換える必要はありません)。
型別-KPI-見本.json — 8 ペイン
{
"date": "2026-08-15 00:00:00",
"pluginName": "kSQL Dashboard Pro",
"pluginVersion": "1",
"engineVersion": "3.66.1",
"appId": 4239,
"appName": "売上明細",
"sqlApps": [
{ "appId": 4239, "appName": "売上明細" },
{ "appId": 4240, "appName": "月次目標" }
],
"config": {
"schemaVersion": 2,
"edition": "pro",
"views": {
"13335807": {
"name": "KPI カードの見本",
"viewName": "【型別】KPI",
"layout": [
{ "i": "pane1", "x": 0, "y": 0, "w": 20, "h": 2 },
{ "i": "pane2", "x": 20, "y": 0, "w": 20, "h": 2 },
{ "i": "pane6", "x": 40, "y": 0, "w": 20, "h": 2 },
{ "i": "pane3", "x": 0, "y": 2, "w": 20, "h": 3 },
{ "i": "pane4", "x": 20, "y": 2, "w": 20, "h": 3 },
{ "i": "pane5", "x": 40, "y": 2, "w": 20, "h": 3 },
{ "i": "pane7", "x": 0, "y": 5, "w": 36, "h": 4 },
{ "i": "pane8", "x": 36, "y": 5, "w": 24, "h": 4 }
],
"panes": [
{
"id": "pane1",
"type": "kpi",
"title": "売上明細の件数",
"sql": "SELECT COUNT(*) FROM APP4239"
},
{
"id": "pane2",
"type": "kpi",
"title": "今月の売上",
"sql": "SELECT SUM(金額) AS メイン値\nFROM APP4239\nWHERE 売上日 = THIS_MONTH() AND 売上ステータス = '確定'",
"options": {
"format": { "scale": "man", "suffix": "円", "decimals": 0 }
}
},
{
"id": "pane3",
"type": "kpi",
"sql": "SELECT\n CONCAT('今月の売上(前月 ',\n FORMAT(ROUND(SUM(CASE WHEN YEAR(売上日) * 12 + MONTH(売上日) = YEAR(CURRENT_DATE()) * 12 + MONTH(CURRENT_DATE()) - 1\n THEN 金額 ELSE 0 END) / 10000), '#,##0'),\n ' 万円)') AS タイトル,\n SUM(CASE WHEN YEAR(売上日) * 12 + MONTH(売上日) = YEAR(CURRENT_DATE()) * 12 + MONTH(CURRENT_DATE())\n THEN 金額 ELSE 0 END) AS メイン値,\n SUM(CASE WHEN YEAR(売上日) * 12 + MONTH(売上日) = YEAR(CURRENT_DATE()) * 12 + MONTH(CURRENT_DATE()) - 1\n THEN 金額 ELSE 0 END) AS 前月値\nFROM APP4239\nWHERE (売上日 = THIS_MONTH() OR 売上日 = LAST_MONTH()) AND 売上ステータス = '確定'",
"options": {
"format": { "scale": "man", "suffix": "円", "decimals": 0 }
}
},
{
"id": "pane4",
"type": "kpi",
"title": "今月の目標達成率",
"sql": "SET @goal = (SELECT SUM(目標金額) FROM APP4240 WHERE 年月 = DATE_FORMAT(CURRENT_DATE(), '%Y-%m'));\nSELECT SUM(金額) AS メイン値, @goal AS 目標値\nFROM APP4239\nWHERE 売上日 = THIS_MONTH() AND 売上ステータス = '確定'",
"options": {
"goalBar": true,
"format": { "scale": "man", "suffix": "円", "decimals": 0 }
}
},
{
"id": "pane5",
"type": "kpi",
"title": "今月の目標達成率(条件付きカラー)",
"sql": "SET @goal = (SELECT SUM(目標金額) FROM APP4240 WHERE 年月 = DATE_FORMAT(CURRENT_DATE(), '%Y-%m'));\nSELECT SUM(金額) AS メイン値, @goal AS 目標値\nFROM APP4239\nWHERE 売上日 = THIS_MONTH() AND 売上ステータス = '確定'",
"options": {
"goalBar": true,
"format": { "scale": "man", "suffix": "円", "decimals": 0 },
"rules": [
{ "target": "rate", "op": "<", "value": 90, "tone": "warn", "apply": "border" },
{ "target": "rate", "op": "<", "value": 70, "tone": "crit", "apply": "border" },
{ "target": "rate", "op": ">=", "value": 100, "tone": "good", "apply": "background" }
]
}
},
{
"id": "pane6",
"type": "kpi",
"title": "自分の担当分(今月)",
"sql": "SELECT SUM(金額) AS メイン値\nFROM APP4239\nWHERE 担当者user in (LOGINUSER()) AND 売上日 = THIS_MONTH() AND 売上ステータス = '確定'",
"options": {
"format": { "scale": "man", "suffix": "円", "decimals": 0 }
}
},
{
"id": "pane7",
"type": "kpi",
"title": "部署別の売上(今月)",
"sql": "SELECT 部署 AS タイトル, SUM(金額) AS メイン値\nFROM APP4239\nWHERE 売上日 = THIS_MONTH() AND 売上ステータス = '確定'\nGROUP BY 部署\nORDER BY メイン値 DESC",
"options": {
"multi": true,
"format": { "scale": "man", "suffix": "円", "decimals": 0 }
}
},
{
"id": "pane8",
"type": "kpi",
"title": "商品カテゴリ別の売上(今年・上位 7 +「その他」)",
"sql": "/* 上位 7 + その他。ペインの maxTiles は「他 N 件」で件数しか出ないため、\n 金額を持った「その他」は SQL 側で作る(レシピ D19) */\nCREATE TEMP TABLE #r AS\n SELECT 商品カテゴリ, SUM(金額) AS 売上\n FROM APP4239\n WHERE 売上日 = THIS_YEAR() AND 売上ステータス = '確定'\n GROUP BY 商品カテゴリ;\nCREATE TEMP TABLE #t AS SELECT 商品カテゴリ, 売上 FROM #r ORDER BY 売上 DESC LIMIT 7;\nSET @other = (SELECT SUM(売上) FROM #r WHERE 商品カテゴリ NOT IN (SELECT 商品カテゴリ FROM #t));\nSELECT 商品カテゴリ AS タイトル, 売上 AS メイン値 FROM #t\nUNION ALL SELECT 'その他' AS タイトル, @other AS メイン値 FROM #t LIMIT 1;",
"options": {
"multi": true,
"maxTiles": 8,
"align": "right",
"format": { "scale": "man", "suffix": "円", "decimals": 0 }
}
}
]
}
},
"common": {}
}
}









