縦持ちのデータに項目追加(計算式)を行いたい


添付画像の左が現在分析設定している画面で下がそのデータのイメージになります。
keyが年月~店名で指標データの項目を縦持ちしてます。
このデータセットにない項目を追加表示したいです。
画像右側がイメージで1人当りの純売上高とう項目でこの項目は純売上高÷営業担当数で
算出します。実現する方法を教えてください。

@kobayashi
ご質問ありがとうございます。

いただいた画面は、指標 列に「純売上高」「営業担当数」「貢献利益」「粗利益」…が 縦持ち(ロング形式) で並んでいるデータですね。この形の場合、純売上高 ÷ 営業担当数 を出すには少し工夫が必要です。ポイントを整理してご説明します。


なぜそのまま割り算できないのか

「純売上高」と「営業担当数」は 別々の行 に入っています。QuickSight の計算フィールドは基本的に「同じ行の中の値どうし」しか直接演算できないため、{当期} / {当期} と書いても、純売上高の行の値を営業担当数の行の値で割ることができません。

そこで、sumIf 関数で「特定の指標の値だけ」を取り出して分子・分母にする のが定石です。


どの方法を選ぶ?(選択フロー・早見表)

実現方法は大きく3系統あります。まずは下のフローで自分に合う方法を選んでください。

早見表

おすすめ度 方法 元データの加工 集計関数 表示のされ方 こんな人向け
★★★(最推奨) 方法A:分析画面で sumIf 不要 使う(分析画面の計算フィールド) 値列として横に追加 まず手早く出したい/データ加工したくない
★★ 方法B:UNION で指標行追加 必要(行を追加) SQL側で計算 指標 列の中に1行として自然に並ぶ イメージ図どおり縦持ちのまま行に差し込みたい
★ 方法C:横持ち化してデータセットで列追加 必要(縦→横に変換) 不要(行レベル) 横持ち前提のレイアウトに列で表示 「集計関数を使わずデータセットで列追加」にこだわりたい

まず迷ったら 方法A が最短です。イメージ図のように「指標列に1行として並べたい」なら 方法B、データセット段階で列として持たせたいなら 方法C を選んでください。

おすすめ度の考え方:★の数は「手軽さ」を基準にしています。

  • 方法A(★★★):元データの加工が一切不要で、計算フィールドを1つ足すだけ。最も手早く、まず試すべき第一選択です。
  • 方法B(★★):イメージ図どおり「指標」列に1行として並べられますが、カスタムSQL でデータ準備側に行を追加する一手間が必要です。
  • 方法C(★):集計関数を使わずデータセットで列にできますが、縦持ち→横持ちへの変換とピボットのレイアウト組み直しが必要で、用途が限定的です。

「データセット作成時にカラムを追加する」でできる?(ご質問への回答)

「集計関数を使わないのであれば、データセット作成時にカラムを追加するのもできそうか?」

結論から言うと、今回の縦持ちデータでは、データセットの計算フィールド(=集計関数を使わない行レベルのカラム追加)では実現できません。 理由は次のとおりです。

補足:QuickSight の集計関数(sum・sumIf など)は分析画面(分析・可視化)でのみ利用でき、データセット準備画面では使えません(データセットの計算フィールドは行レベル専用です)。そのため、そもそもデータセット画面では sumIf による割り算は書けず、「集計関数を使わない行レベルのカラム追加」でも今回の割り算は実現できません。

なぜ行レベルのカラム追加では実現できないのか

QuickSight の計算フィールドは、作る場所(データセット画面か分析画面か)で使える関数が異なります。

  • データセット準備画面の計算フィールドは「行レベル」専用 です。sum・avg・sumIf などの集計関数はデータセット画面では使えません(エラーになります)。行レベルの式は、ソースデータ1行の中にある値どうし しか演算できません。
  • 集計関数(sum・sumIf など)は分析画面(分析・可視化)でのみ利用可能 です。今回の sumIf を使う方法A(後述)は、この分析画面での計算フィールドにあたります。

今回「集計関数を使わずに」データセットでカラムを追加したい、というご要望はこの後者にあたります。ところが今回のデータは縦持ちで、

  • 「純売上高」の値 → ある行(指標='純売上高')
  • 「営業担当数」の値 → 別の行(指標='営業担当数')

のように 別々の行 に分かれています。したがって、純売上高 ÷ 営業担当数 を計算しようにも、1つの行の中に「純売上高」と「営業担当数」が同時に存在しない ため、行レベルの式(集計関数なし)では割り算そのものが書けません。

年月    店名    指標        当期
202304  ABC東局 純売上高    10000    ← この行には「営業担当数」が無い
202304  ABC東局 営業担当数  10       ← この行には「純売上高」が無い

→ どちらの行から見ても相方の値が無いので、行レベルでは 10000 / 10 を作れない。

では「データセットでカラムを追加」を成立させるには?

「データセット作成時にカラムを追加する」という発想自体は正しく、それを成立させる方法は「元データを横持ちにしてから列を足す」または「SQL/ETLで指標行を追加する」 ことになります。つまり、

  • もしデータが横持ち(1行に 純売上高 列と 営業担当数 列が並ぶ形)だったなら → データセットの計算フィールドで {純売上高} / {営業担当数} と 集計関数なしの行レベルカラムを普通に追加できます。これはまさにご質問のイメージどおりのやり方です。
  • しかし今回は縦持ちなので、そのままではこの手が使えず、下記のいずれかが必要になります:
    1. 方法C:横持ち化してから列追加(元テーブルを 指標 でピボットして 純売上高 営業担当数 を列にし、純売上高/営業担当数 列を足す)
    2. 方法B:縦持ちのまま指標行を追加(指標='1人当りの純売上高' の行を UNION で足す)
    3. 方法A:分析画面で sumIf を使う(データを縦持ちのまま QuickSight で出したいだけなら最短)

まとめ:ご質問への直接の回答

前提 データセットで集計関数なしのカラム追加は可能?
データが横持ち(純売上高・営業担当数が別々の列) :white_check_mark: 可能。{純売上高}/{営業担当数} を行レベルで追加できる
データが縦持ち(今回のケース。指標が別々の行) :cross_mark: 不可。行レベルでは相方の値が同じ行に無いため割り算できない

→ 今回の縦持ちのままカラム追加したいなら、まず元データ側で横持ちにする(または指標行を追加する)加工が前提 になります。加工を避けたい場合は、分析画面の sumIf(方法A)が最短です。


方法A(推奨):sumIf で指標を横断取得して割る

分析画面で 計算フィールド を新規作成し、次のように定義します。

フィールド名:1人当りの純売上高(当期)

sumIf({当期}, {指標} = '純売上高')
/
sumIf({当期}, {指標} = '営業担当数')
  • sumIf({当期}, {指標} = '純売上高') … 「純売上高」の行の当期値だけを合計
  • sumIf({当期}, {指標} = '営業担当数') … 「営業担当数」の行の当期値だけを合計
  • それを割ることで「1人当りの純売上高」になります

前期分も同様に、{前期} に置き換えて 1人当りの純売上高(前期) を作成してください。

sumIf({前期}, {指標} = '純売上高')
/
sumIf({前期}, {指標} = '営業担当数')

:warning: 指標名('純売上高' '営業担当数')は、データセット内の実際の値と 完全一致(表記ゆれ・空白に注意)させてください。

:light_bulb: 営業担当数が 0 の場合、割り算の結果は空白(undefined)になります。0 を表示させたい場合は ifelse でガードできます(QuickSight 公式推奨):

ifelse(
  sumIf({当期}, {指標} = '営業担当数') = 0,
  0,
  sumIf({当期}, {指標} = '純売上高') / sumIf({当期}, {指標} = '営業担当数')
)

表示のさせ方(ここが実は肝心です)

ここで注意点があります。現状のピボットは 指標 を行に置いて縦に積む 構成なので、「1人当りの純売上高」も既存の指標と同じ列に 1行として 並べたい、というのがご要望のイメージ図ですね。

ただ、方法Aで作った計算フィールドは メジャー(値) であって 指標 ディメンションの1メンバーではないため、そのままでは「純売上高・営業担当数…」の下に自然な1行としては入りません。実現方法は次の2通りです。

方法Aの表示方法①:値フィールドとして横に追加(最も簡単・推奨)

ピボットの 「値」 に 1人当りの純売上高(当期)/(前期) を追加します。集計は 合計(Sum) または 最小(Min) を選べば、sumIf の結果がそのまま表示されます(値は1つなのでどちらでも同じ)。

  • メリット:計算フィールドを足すだけで完了。壊れにくい。
  • 見え方:既存の指標行の右側(または別の値列)に「1人当り純売上高」が並びます。

方法Aの表示方法②:既存指標と同じ「行」に差し込みたい場合 → 方法Bへ

イメージ図のように 指標 列の中に “1人当りの純売上高” という行を作りたい場合は、データ準備(データセット編集)側で行を増やす 必要があります。計算フィールドだけでは既存ディメンションに新メンバーを追加できないためです。代表的なやり方:

  • SQL / データ準備で UNION … 元データに「指標=‘1人当りの純売上高’, 当期=純売上高/営業担当数」という行を追加生成する
  • 縦持ちの元テーブルに、指標名を持つ計算行をあらかじめ作っておく

方法B(後述)は元データの作り込みが必要なので、まずは上記①の値として表示できるかご確認いただくのがおすすめ です。見た目上の「行に入れたい」というご要望が強い場合のみ方法Bをご検討ください。

方法B:カスタム SQL の UNION ALL で指標行を追加(具体例)

データソースが Athena・Redshift・RDS 等で カスタム SQL が使える場合、元の縦持ちテーブルに「1人当りの純売上高」という指標行を UNION で追加します。イメージは「既存の全指標行はそのまま残し、年月+支社コード+…+店名 の粒度ごとに算出した1行を継ぎ足す」形です。

元テーブル(例): sales_long(列: 年月, 支社コード, 拠点コード, 企業コード, 口座, 支社, 店名, 指標, 当期, 前期)

-- ① 既存の縦持ちデータは全てそのまま残す
SELECT
    年月, 支社コード, 拠点コード, 企業コード, 口座, 支社, 店名,
    指標,
    当期,
    前期
FROM sales_long

UNION ALL

-- ② 「1人当りの純売上高」の行を、キー粒度ごとに算出して追加
SELECT
    年月, 支社コード, 拠点コード, 企業コード, 口座, 支社, 店名,
    '1人当りの純売上高' AS 指標,
    MAX(CASE WHEN 指標 = '純売上高'   THEN 当期 END)
      / NULLIF(MAX(CASE WHEN 指標 = '営業担当数' THEN 当期 END), 0) AS 当期,
    MAX(CASE WHEN 指標 = '純売上高'   THEN 前期 END)
      / NULLIF(MAX(CASE WHEN 指標 = '営業担当数' THEN 前期 END), 0) AS 前期
FROM sales_long
GROUP BY
    年月, 支社コード, 拠点コード, 企業コード, 口座, 支社, 店名

ポイント:

  • CASE WHEN ... THEN 当期 END で、同じキーの中から「純売上高」「営業担当数」の値をそれぞれ横に取り出します(縦→横のピボット)。
  • MAX() は、各キー×指標につき値が1つである前提で「その1件を拾う」ために使っています(MIN でも可)。
  • NULLIF(..., 0) で 営業担当数が0のときのゼロ除算を回避(結果は NULL になります)。
  • GROUP BY には 指標 を含めない(キー粒度で1行に集約したいため)。それ以外のキー列はすべて列挙します。
  • 整数割り算に注意:当期・前期 が整数型の DB(Redshift・PostgreSQL・SQL Server)では、10000 / 3 が 3333(小数切り捨て)になります。小数を保持したい場合は分子を数値型にキャストしてください(DB別の書き方は後述の構文バリエーション参照)。
  • 追加したいのが当期のみなら前期の行は省略可。逆に他の派生指標(例: 1人当り貢献利益)も同様に UNION ALL を重ねれば増やせます。

:white_check_mark: 上記 SQL は実データで動作確認済みです。例:純売上高 10000/営業担当数 10 → 1000、営業担当数が 0 の行は NULLIF により結果が NULL(ゼロ除算エラーにならない)。

:light_bulb: CSV/Excel など SQL が使えないデータソースの場合は、この「縦→横→再計算→縦」の加工を事前に(Excel の数式や ETL 側で)行い、指標='1人当りの純売上高' の行を含んだファイルを取り込む、という考え方は同じです。

この方式なら、ピボットの 指標 行に既存指標と並んで「1人当りの純売上高」が自然な1行として表示され、イメージ図どおりの見た目になります。

方法B:特定 DB 向けの構文バリエーション

上記の一般的な SQL(CASE WHEN + MAX + NULLIF)は標準 SQL なので、主要なデータベースでほぼそのまま動きます。ただし ゼロ除算の扱い や 条件付き集計の書き方 に DB ごとの流儀があります。UNION の②側(「1人当りの純売上高」を算出する SELECT)の当期の式だけ抜粋して、DB別に示します(前期も 当期→前期 に置き換えれば同様)。

■ Amazon Athena / Presto / Trino

標準構文がそのまま使えます。NULLIF によるゼロ除算回避も有効です。条件付き集計を FILTER 句で書くとより読みやすくなります。

-- 標準の CASE WHEN 版(そのまま動作)
MAX(CASE WHEN 指標 = '純売上高'   THEN 当期 END)
  / NULLIF(MAX(CASE WHEN 指標 = '営業担当数' THEN 当期 END), 0) AS 当期

-- FILTER 句を使った同等の書き方(Presto/Trino/Athena engine v3)
MAX(当期) FILTER (WHERE 指標 = '純売上高')
  / NULLIF(MAX(当期) FILTER (WHERE 指標 = '営業担当数'), 0) AS 当期

※ '純売上高' '営業担当数' は 列の値 との比較なのでシングルクォートのままで問題ありません。一方、列名(当期・指標 など)側に予約語や特殊文字が含まれる場合は、その列名をダブルクォート "..." で囲みます。

■ Amazon Redshift

標準構文で動作します。ゼロ除算は NULLIF に加えて DECIMAL へのキャストを入れると整数割り算による切り捨てを防げます。

MAX(CASE WHEN 指標 = '純売上高'   THEN 当期 END)::DECIMAL(18,4)
  / NULLIF(MAX(CASE WHEN 指標 = '営業担当数' THEN 当期 END), 0) AS 当期

※ 当期 が整数型の場合、キャストなしだと 10000 / 3 = 3333(小数切り捨て)になります。::DECIMAL で小数を保持してください。

■ MySQL / Aurora MySQL

NULLIF が使えます。MySQL のゼロ除算の挙動は SQL モード依存で、strict モードでは ERROR 1365 (Division by 0) になり、非 strict モードでは NULL を返します。挙動が環境によって変わるため、NULLIF で明示的に回避するのが安全です。

MAX(CASE WHEN 指標 = '純売上高'   THEN 当期 END)
  / NULLIF(MAX(CASE WHEN 指標 = '営業担当数' THEN 当期 END), 0) AS 当期

※ MySQL は FILTER 句非対応のため CASE WHEN 版を使用してください。

■ Microsoft SQL Server

NULLIF が使えます。整数割り算の切り捨てを避けるため、分子を DECIMAL/FLOAT にキャストします。

MAX(CASE WHEN 指標 = N'純売上高'   THEN 当期 END) * 1.0
  / NULLIF(MAX(CASE WHEN 指標 = N'営業担当数' THEN 当期 END), 0) AS 当期

※ 日本語のリテラルには N'...'(Unicode リテラル) を付けるのが安全です。* 1.0 で暗黙的に浮動小数へ変換し、整数割り算を回避しています。

■ PostgreSQL / Aurora PostgreSQL

標準構文および FILTER 句の両方が使えます。整数割り算を避けるため数値型へキャストします。

-- FILTER 句版
MAX(当期) FILTER (WHERE 指標 = '純売上高')::NUMERIC
  / NULLIF(MAX(当期) FILTER (WHERE 指標 = '営業担当数'), 0) AS 当期

DB別 差分まとめ

DB 条件付き集計 ゼロ除算回避 整数割り算対策 日本語リテラル
Athena / Presto / Trino CASE WHEN または FILTER 句 NULLIF 通常は自動的にdouble '...' でOK
Redshift CASE WHEN NULLIF ::DECIMAL キャスト推奨 '...' でOK
MySQL CASE WHEN のみ NULLIF(strictモードは0除算エラー・非strictはNULL。明示推奨) 演算で自動的に小数 '...' でOK
SQL Server CASE WHEN NULLIF * 1.0 または CAST(... AS DECIMAL) N'...' 推奨
PostgreSQL CASE WHEN または FILTER 句 NULLIF ::NUMERIC キャスト推奨 '...' でOK

方法C:横持ち化してからデータセットで列を追加する(集計関数を使わない列追加)

「データセットで集計関数を使わずにカラムを追加したい」というご要望を優先する場合は、元データを横持ちにしてから 純売上高 / 営業担当数 列を足す構成になります。SQL で縦→横に変換する例:

-- 縦持ち sales_long を、キー粒度で横持ち wide に変換
SELECT
    年月, 支社コード, 拠点コード, 企業コード, 口座, 支社, 店名,
    MAX(CASE WHEN 指標 = '純売上高'   THEN 当期 END) AS 純売上高_当期,
    MAX(CASE WHEN 指標 = '営業担当数' THEN 当期 END) AS 営業担当数_当期
FROM sales_long
GROUP BY
    年月, 支社コード, 拠点コード, 企業コード, 口座, 支社, 店名

この結果(横持ち)をデータセットに取り込めば、データセットの計算フィールドで集計関数なしの行レベル式 として

{純売上高_当期} / {営業担当数_当期}

と 1人当りの純売上高(当期) カラムを普通に追加できます。ただしこの場合、ピボットの構成自体が縦持ち前提から横持ち前提に変わる点にご注意ください(既存の 指標 行ベースのレイアウトを組み直す必要があります)。

方法C:横持ち化 SQL の特定 DB 向け構文バリエーション

上記の縦→横変換(CASE WHEN + MAX)は標準 SQL なので大半の DB でそのまま動きますが、DB によっては専用の PIVOT 構文や FILTER 句でより簡潔に書けます。以下、当期・営業担当数の2列を取り出す例(前期や他指標も同様に列を増やせます)。

■ Amazon Athena / Presto / Trino

標準の CASE WHEN 版に加え、FILTER 句が使えます。

SELECT
    年月, 支社コード, 拠点コード, 企業コード, 口座, 支社, 店名,
    MAX(当期) FILTER (WHERE 指標 = '純売上高')   AS 純売上高_当期,
    MAX(当期) FILTER (WHERE 指標 = '営業担当数') AS 営業担当数_当期
FROM sales_long
GROUP BY 年月, 支社コード, 拠点コード, 企業コード, 口座, 支社, 店名

■ Amazon Redshift

PIVOT 構文(または標準の CASE WHEN)が使えます。

-- CASE WHEN 版(推奨・確実)
SELECT
    年月, 支社コード, 拠点コード, 企業コード, 口座, 支社, 店名,
    MAX(CASE WHEN 指標 = '純売上高'   THEN 当期 END) AS 純売上高_当期,
    MAX(CASE WHEN 指標 = '営業担当数' THEN 当期 END) AS 営業担当数_当期
FROM sales_long
GROUP BY 年月, 支社コード, 拠点コード, 企業コード, 口座, 支社, 店名

■ MySQL / Aurora MySQL

FILTER 句・PIVOT は非対応のため CASE WHEN 版を使用します。

SELECT
    年月, 支社コード, 拠点コード, 企業コード, 口座, 支社, 店名,
    MAX(CASE WHEN 指標 = '純売上高'   THEN 当期 END) AS 純売上高_当期,
    MAX(CASE WHEN 指標 = '営業担当数' THEN 当期 END) AS 営業担当数_当期
FROM sales_long
GROUP BY 年月, 支社コード, 拠点コード, 企業コード, 口座, 支社, 店名

■ Microsoft SQL Server

専用の PIVOT 演算子が使えます(CASE WHEN 版も可)。日本語リテラルには N'...' を付けます。

-- PIVOT 演算子版
SELECT 年月, 支社コード, 拠点コード, 企業コード, 口座, 支社, 店名,
       [純売上高] AS 純売上高_当期, [営業担当数] AS 営業担当数_当期
FROM (
    SELECT 年月, 支社コード, 拠点コード, 企業コード, 口座, 支社, 店名, 指標, 当期
    FROM sales_long
) src
PIVOT (
    MAX(当期) FOR 指標 IN ([純売上高], [営業担当数])
) pvt

-- CASE WHEN 版(N'...' に注意)
SELECT
    年月, 支社コード, 拠点コード, 企業コード, 口座, 支社, 店名,
    MAX(CASE WHEN 指標 = N'純売上高'   THEN 当期 END) AS 純売上高_当期,
    MAX(CASE WHEN 指標 = N'営業担当数' THEN 当期 END) AS 営業担当数_当期
FROM sales_long
GROUP BY 年月, 支社コード, 拠点コード, 企業コード, 口座, 支社, 店名

■ PostgreSQL / Aurora PostgreSQL

標準の CASE WHEN・FILTER 句が使えます(tablefunc 拡張の crosstab もありますが、FILTER 句の方が手軽です)。

SELECT
    年月, 支社コード, 拠点コード, 企業コード, 口座, 支社, 店名,
    MAX(当期) FILTER (WHERE 指標 = '純売上高')   AS 純売上高_当期,
    MAX(当期) FILTER (WHERE 指標 = '営業担当数') AS 営業担当数_当期
FROM sales_long
GROUP BY 年月, 支社コード, 拠点コード, 企業コード, 口座, 支社, 店名

横持ち化 SQL の DB別 差分まとめ

DB 縦→横変換の書き方 備考
Athena / Presto / Trino CASE WHEN または MAX(...) FILTER (WHERE ...) PIVOT 構文はなし
Redshift CASE WHEN(PIVOT 構文も可) CASE WHEN が確実
MySQL / Aurora MySQL CASE WHEN のみ FILTER・PIVOT 非対応
SQL Server PIVOT 演算子 または CASE WHEN 日本語リテラルは N'...'
PostgreSQL CASE WHEN または FILTER 句(crosstab も可) FILTER 句が手軽

横持ち化した後、純売上高_当期 / 営業担当数_当期 の割り算列を SQL 側で足す場合は、前述「特定 DB 向けの構文バリエーション」のゼロ除算回避(NULLIF)・整数割り算対策(キャスト)がそのまま当てはまります。データセットの計算フィールド側で割る場合はそれらの考慮は QuickSight が扱います。

各構文の公式ドキュメント(参考)

「率」列(当期比・前期比)も出したい場合

イメージ右側にある「率」列(例:貢献利益 2%)と同じように、1人当りの伸び率を出したい場合は:

(
  {1人当りの純売上高(当期)} - {1人当りの純売上高(前期)}
)
/ {1人当りの純売上高(前期)}

を計算フィールドとして追加し、書式をパーセント表示にしてください。


まとめ

やりたいこと 方法
純売上高 ÷ 営業担当数 を算出 sumIf({当期}, {指標}='純売上高') / sumIf({当期}, {指標}='営業担当数')
ピボットに表示(かんたん) 計算フィールドを「値」に追加(集計=合計 or 最小)
指標列の中に1行として差し込む(方法B) データ準備側でカスタム SQL の UNION ALL により指標行を追加(下記SQL例参照)
当期比・前期比 当期・前期の計算フィールド同士で差分÷前期

まずは 方法A(値として表示) をお試しいただき、表示位置のご要望(行に差し込みたい等)があれば、データ準備側での対応(方法B)に進む、という流れがスムーズです。

ご不明点があればお気軽にどうぞ。


参考情報(公式ドキュメント)

回答ありがとうございます。試してみます。

@kobayashi さま
回答をご確認いただき、その後いかかがでしょうか。

もし問題が解決されているようでしたら、Solutionボタンで本お問い合わせをクローズいただけますと幸いです。

それでは、よろしくお願いいたします。