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 デモ ① KPI カード — 数字 1 つを、意味のある 1 つに

0
Last updated at Posted at 2026-08-17

kintone の一覧に数字を 1 つ大きく出す。それだけなら難しくありません。難しいのは、その数字を見た人が次の行動を決められる形にすることです。「今月 810 万円」だけでは、それが good なのか bad なのか分かりません。先月と比べてどうか、目標に対してどこまで来たかが添えられて初めて意味を持ちます。

kSQL Dashboard Pro の KPI カードは、そのための道具立てを持っています。この記事では SQL・ペイン設定・表示結果を 1 セットにして、8 種類の KPI カードを作ります。SQL はすべてコピペで動きます。

kSQL Dashboard Pro デモ(全 5 回)
⓪ 準備編 / ① KPI カード(この記事) / ② データ表 / ③ グラフ / ④ マークダウン

**アプリの用意は ⓪ 準備編**にまとめてあります(5 本とも同じアプリを使います)。

この連載で作るダッシュボードの全体(KPI・マークダウン・グラフ・表)

上はこの連載で作るダッシュボードの全体です。この記事はこのうち 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 カードが返すべき列の形

KPI カードの表示例 — この記事で作る 8 枚の全体

表示タイプごとに「返すべき列の形」が決まっています。ここがずれると、SQL は通るのに描画だけ壊れます。KPI カードの決まりはこれだけです。

結果は 1 行。列の名前で役割が決まる。

2 行以上返しても 1 行目しか使いません(タイル表示だけは例外。後述)。

最小 — 3 行で出る

SELECT COUNT(*) FROM APP4239

「売上明細の件数」1,500 — 列名を付けなくても 1 列目がメイン値になる

ペインの表示タイプは「KPI カード」を選びます(設定画面の表示タイプ欄)。SQL はこれだけです。

役割を示す列名がないときは 1 列目がメイン値になります。AS メイン値 を書く必要はありません。

予約列名 — 名前を付けると役割が付く

列に決まった名前を付けると、カードがその役割で扱います。大文字小文字は区別しません。

役割 予約列名 効果
メイン値 メイン値 / value / main 巨大表示。未解決なら 1 列目
タイトル タイトル / title カード見出し(ペインの設定が優先)
比較値 比較値 / delta / comparison そのまま増減として表示
基準値 基準値 / 前月値 / base / previous メイン値との増減率を自動計算
目標値 目標値 / target / goal 達成率を自動計算
達成率 達成率 / rate / achievement サブ情報として表示

役割の決まり方には順番があります。

  1. 列の割り当て(設定画面で列名を直接指定)
  2. 予約列名
  3. 1 列目フォールバック

つまり、SQL の別名を予約列名にしておけば設定は要りません。設定で明示したいときだけ 1 を使います。

比較値と基準値は排他です。 比較値があると「そのまま表示」になり、自動計算の % は出ません。基準値だと % だけが出て、元の金額は出ません。同じ枠に両方は出せません。


例 1: 今月の売上 — 相対日付で絞る

SELECT SUM(金額) AS メイン値
FROM APP4239
WHERE 売上日 = THIS_MONTH() AND 売上ステータス = '確定'

表示タブでフォーマットを指定します。

設定
表示スケール
単位(接尾)
小数桁 0

「今月の売上」810万円

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 売上ステータス = '確定'

「今月の売上(前月 3,412 万円)」810万円 ▼76.3%

要点は 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 売上ステータス = '確定'

表示タブで「達成率をプログレスバーで表示」をオンにします。

「今月の目標達成率」810万円 + バー 79%

; で区切った複文がペインでそのまま書けます。描画されるのは最後に結果セットを返した文で、SET は結果を作りません。

目標値 を返すとカードが達成率を自動計算します。自分で割り算を書く必要はありません。

JOIN でも書けますが、この用途では SET のほうが素直です。 JOIN にすると明細 1 行ごとに目標行が付くため、目標の合計を取るのに MAX() を挟むなどの工夫が要ります。「全体の目標額」という 1 つの値がほしいだけなら、先に変数へ入れてしまうほうが読めます。


例 4: 条件付きカラー — 閾値で色を変える

例 3 とまったく同じ SQLに、色のルールを足します。

target op value tone apply
達成率 < 90 注意
達成率 < 70 重大
達成率 >= 100 良好 背景

「今月の目標達成率(条件付きカラー)」79% で注意の枠が出ている

  • 判定対象はメイン値達成率
  • 上から順に評価し、同じ適用先(文字・背景・枠)は後のルールが優先します
  • ルールは 10 個まで

79% なので「注意」の枠が付いています。100% を超えると背景が変わります。

色だけに意味を持たせないほうが安全です。この例では枠と背景で見分けが付きますが、達成率の数字自体もバーの右に出ているので、色が分からない環境でも読めます。


例 5: 自分の担当分だけ表示する

LOGINUSER() を使うと、閲覧者ごとに内容が変わるカードになります。共通のダッシュボードに置いても、見る人それぞれの数字が出ます。

SELECT SUM(金額) AS メイン値
FROM APP4239
WHERE 担当者user in (LOGINUSER()) AND 売上日 = THIS_MONTH() AND 売上ステータス = '確定'

「自分の担当分(今月)」125万円

書き方に決まりがあります。

  • = は使えません。 必ず in / not in を使います
  • 対象は 作成者 / 更新者 / ユーザー選択フィールドだけです。文字列の氏名フィールドには使えません
  • in (LOGINUSER(), 'taro') のように他の値と混ぜられません

担当者user を別に持っているのはこのためです。 このアプリには氏名の文字列 担当者名 もありますが、LOGINUSER() はユーザー選択にしか効きません。逆に、集計やランキング、別アプリとの突き合わせは文字列のほうが素直に書けます。役割の違う 2 列を持つのは、実務でもよくある形です。

組織で絞る場合の注意

同じ発想で PRIMARY_ORGANIZATION() を使うと、自分の組織のデータだけを出せます。ただし落とし穴があります

優先組織が設定されていない利用者では、この条件が無視され、全レコードが返ります。

kintone の仕様で、エンジンからもプラグインからも「条件が無視された」ことを判別できません。つまり 「自組織の実績」のつもりの数字が、その人には全社の数字として見えます

kSQL Dashboard Pro は一覧画面で、優先組織が無い利用者にだけそのペインへ警告を出します。それでも、書く側でも次のどちらかを取ってください。

  1. ペインのタイトルに前提を書く — 自組織の売上(優先組織の設定が前提)
  2. 配布前に、対象の利用者に優先組織が設定されていることを確認する

例 6: 複数行タイル — 1 枚に複数の数字を並べる

**表示タブの「複数行をタイル表示」**をオンにすると、結果の各行がミニタイルで並びます。

SELECT 部署 AS タイトル, SUM(金額) AS メイン値
FROM APP4239
WHERE 売上日 = THIS_MONTH() AND 売上ステータス = '確定'
GROUP BY 部署
ORDER BY メイン値 DESC

「部署別の売上(今月)」5 タイル

予約列名の無い 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;

「商品カテゴリ別の売上(今年・上位 7 +「その他」)」4 列 × 2 段。その他 6,083万円

一時テーブルを 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 列目がメイン値
  2. 基準値を返せば増減率、目標値を返せば達成率が自動で出る — 割り算を自分で書かない
  3. 相対日付で絞る — 速度の話ではなく、そのペインが将来も動くかどうかの話

そのうえで、条件付きカラーで「見るべきカード」を目立たせ、タイル表示で 1 枚にまとめる。ここまでで、一覧の上に置く数字はだいたい作れます。

関連記事


付録 — 配布ファイル

GitHub に一式を置いています

アプリテンプレート・データ投入スクリプト・設定 JSON をまとめてあります。

https://github.com/rex0220/ksql-dashboard-pro-demo

**使い方は ⓪ 準備編にまとめてあります。**設定はまとめて 1 回で取り込めます(settings/型別-全一覧-見本.json に 5 一覧・43 ペイン)。この記事のぶんだけでよければ、下の JSON を使ってください。

設定 JSON(この記事の 8 ペイン)

GitHub の settings/型別-KPI-見本.json と同じものです。コピーして使えるよう全文を貼ります。

  1. 折りたたみを開き、コードブロック右上のコピーボタンでコピー
  2. テキストエディター(メモ帳等)に貼り付け、型別-KPI-見本.json として保存(文字コード UTF-8)
  3. 設定画面の ツール → インポート でそのファイルを選択

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": {}
  }
}
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?