コンテンツにスキップ

items.Sum

概要

itemsオブジェクトの「Sumメソッド」です。指定したテーブルにある対象の数値項目の合計値を取得します。選択条件を指定して集計対象のレコードを絞り込むことができます。

制限事項

  1. 数値項目以外では使用できません。

構文

items.Sum(siteId, columnName, view)

パラメータ

パラメータ 型 必須 説明
siteId object ○ 対象テーブルのサイトIDを指定
columnName string ○ 計算対象の数値項目を指定
view string - 選択するレコードの条件を指定

戻り値

対象テーブルの指定した数値項目の合計値を返却します。

使用例

以下の例では、サイトIDが 2 のテーブルのNumA(数値A)の合計値を取得します。

JavaScript
let sum = items.Sum(2, 'NumA');

以下の例では、サイトIDが 2 のテーブルでStatus(状況)が900(完了)のレコードのNumA(数値A)の合計値を取得します。

let view = {
    "View": {
        "ColumnFilterHash": {
            "Status": "[\"900\"]"
        }
    }
};
let sum = items.Sum(2, 'NumA', JSON.stringify(view));
context.Log(sum);

サンプルコード

1. 3階層以上のサマリ処理
概要

標準機能では実現できない、3階層以上のサマリ処理を実現します。このサンプルでは、作業実績→プロジェクト→部門の3テーブルを用意し、実績工数をタスク単位→プロジェクト単位→部門単位とサマリしていきます。

実務での使いどころ

作業実績を、プロジェクト単位、さらに部門単位へと積み上げて、部門ごとの工数を集計したいときに使えます。標準のサマリでは実現できない 3 階層の集計のため、部門別の予実管理や稼働の集計を表計算ソフトでまとめ直している場合に役立ちます。

サマリのイメージは以下のとおりで、サーバスクリプトは孫である作業実績テーブルへ設定します。

3階層のサマリのイメージ図。作業実績テーブルからプロジェクトテーブル、部門テーブルへと工数を積み上げる

使用テーブル

以下のようなテーブルを用意します。

〇部門テーブル

項目 表示名 備考
Title 部門
NumA 月間総工数(時間) 積み上げ先
NumB 月間予算工数(時間) 手動入力

〇プロジェクトテーブル

項目 表示名 備考
Title プロジェクト
ClassA 部門 部門テーブルへのリンク項目
NumA プロジェクト総工数(時間) 積み上げ先

〇作業実績テーブル

項目 表示名 備考
Title タスク名
ClassA プロジェクト プロジェクトテーブルへのリンク項目
NumA 作業時間(時間) 入力値

サンプルコード内で、サイト名「作業実績」「プロジェクト」で制御しているため、実際のテーブル設定に合わせて調整してください。

HIERARCHY_CONFIG 仕様

HIERARCHY_CONFIG は階層サマリの動作をすべて制御する定義体です。この配列のみを編集することで、テーブル構成・集計内容・階層数を自由にカスタマイズできるようになっています。

〇構造

const HIERARCHY_CONFIG = [
    // 第1階層(最下位テーブルから1つ上へ)
    {
        childSiteName: '集計元テーブル名',
        linkColumn:    '親へのリンク列名',
        operations: [
            { 
                aggregateType: AGGREGATE_TYPE.SUM, 
                sourceColumn: '集計列名', 
                writeColumn: '書き込み列名' 
            },
        ],
    },
    // 第2階層(さらに1つ上へ)
    {
        childSiteName: '集計元テーブル名',
        linkColumn:    '親へのリンク列名',
        operations: [
            {
                aggregateType: AGGREGATE_TYPE.SUM, 
                sourceColumn: '集計列名', 
                writeColumn: '書き込み列名' },
        ],
    },
    // 以降、必要な階層数だけ追加
];

〇階層レベルのプロパティ

プロパティ 型 必須 説明
childSiteName string ✓ 集計元テーブルのサイト名。items.GetClosestSite() で解決される
linkColumn string ✓ 子テーブルが親レコードを指すリンク列名。次の階層への辿りにも使用される
operations array ✓ 同階層内で実行する集計操作の一覧(1件以上)

〇operations のプロパティ

プロパティ 型 必須 説明
aggregateType 定数 ✓ 集計種別。AGGREGATE_TYPE 定数を使用する
sourceColumn string △ 集計対象の数値列名。AGGREGATE_TYPE.COUNT の場合は省略可
writeColumn string ✓ 集計結果を書き込む親テーブル側の列名

〇AGGREGATE_TYPE 定数

定数 処理内容 sourceColumn
AGGREGATE_TYPE.COUNT レコード件数を集計 省略可
AGGREGATE_TYPE.SUM 数値列の合計を集計 必須
AGGREGATE_TYPE.AVERAGE 数値列の平均を集計 必須
AGGREGATE_TYPE.MIN 数値列の最小値を集計 必須
AGGREGATE_TYPE.MAX 数値列の最大値を集計 必須
動作の仕様
  • 配列は 下位テーブルから上位テーブルへの順 に記述する
  • linkColumn は「子が親を絞り込む列」と「親から次の親IDを辿る列」を兼ねる
  • いずれかの階層でエラーが発生した場合、以降の階層処理は中断される
設定例:同一階層で複数の集計を行う場合
{
    childSiteName: '作業実績',
    linkColumn:    'ClassA',
    operations: [
        { 
            aggregateType: AGGREGATE_TYPE.SUM,     
            sourceColumn: 'NumA', 
            writeColumn: 'NumA' 
        }, // 合計
        { 
            aggregateType: AGGREGATE_TYPE.AVERAGE, 
            sourceColumn: 'NumA', 
            writeColumn: 'NumB' 
        }, // 平均
        { 
            aggregateType: AGGREGATE_TYPE.COUNT,
            writeColumn: 'NumD' 
        }, // 件数
    ],
},
JavaScript

条件:作成後、更新後

// ============================================================
// 集計種別定数
// ============================================================
const AGGREGATE_TYPE = {
    COUNT: 'count',
    SUM: 'sum',
    AVERAGE: 'average',
    MIN: 'min',
    MAX: 'max',
};
// ============================================================
// 配列の各要素が1つの「階層(レベル)」を表す。
// 同じ階層に複数の操作を並べても、親レコードの取得・更新は1回にまとめて実行される。
// 階層の追加・変更はこの定義のみ修正すればよい。
// ============================================================
const HIERARCHY_CONFIG = [
    // 第1階層: 作業実績 → プロジェクト
    {
        childSiteName: '作業実績',
        linkColumn: 'ClassA',
        operations: [
            {
                aggregateType: AGGREGATE_TYPE.SUM,
                sourceColumn: 'NumA',
                writeColumn: 'NumA',
            },
        ],
    },
    // 第2階層: プロジェクト → 部門
    {
        childSiteName: 'プロジェクト',
        linkColumn: 'ClassA',
        operations: [
            {
                aggregateType: AGGREGATE_TYPE.SUM,
                sourceColumn: 'NumA',
                writeColumn: 'NumA',
            },
        ],
    },
    // 第3階層以上に拡張する場合はここに追加する
];
// ============================================================
// サイトID取得(キャッシュ付き)
// ============================================================
const _siteIdCache = new Map();
function getSiteIdOrNull(siteName) {
    if (_siteIdCache.has(siteName)) return _siteIdCache.get(siteName);
    const site = items.GetClosestSite(siteName);
    if (!site || !site.SiteId) {
        logs.LogUserError(
            `サイト情報取得失敗: ${siteName}`,
            getSiteIdOrNull.name,
        );
        return null;
    }
    _siteIdCache.set(siteName, site.SiteId);
    return site.SiteId;
}
// ============================================================
// 集計種別ディスパッチ
// ============================================================
function aggregate(type, siteId, sourceColumn, view) {
    switch (type) {
        case AGGREGATE_TYPE.COUNT:
            return items.Count(siteId, view) || 0;
        case AGGREGATE_TYPE.SUM:
            return items.Sum(siteId, sourceColumn, view) || 0;
        case AGGREGATE_TYPE.AVERAGE:
            return items.Average(siteId, sourceColumn, view) || 0;
        case AGGREGATE_TYPE.MIN:
            return items.Min(siteId, sourceColumn, view) || 0;
        case AGGREGATE_TYPE.MAX:
            return items.Max(siteId, sourceColumn, view) || 0;
        default:
            logs.LogUserError(`不明な aggregateType: ${type}`, aggregate.name);
            return null;
    }
}
// ============================================================
// 階層サマリ実行
// ============================================================
function runHierarchySummary(config, startParentId) {
    let currentParentId = startParentId;
    for (const level of config) {
        if (!currentParentId) {
            logs.LogUserError(
                '親IDが取得できないため処理を中断します',
                runHierarchySummary.name,
            );
            break;
        }
        const siteId = getSiteIdOrNull(level.childSiteName);
        if (!siteId) break;
        // 親レコードを階層ごとに1回だけ取得する
        const parent = Array.from(items.Get(Number(currentParentId)))[0];
        if (!parent) {
            logs.LogUserError(
                `親レコード取得失敗: ID=${currentParentId}`,
                runHierarchySummary.name,
            );
            break;
        }
        // 同階層の全operationsを集計し、親レコードに値をセット
        let hasError = false;
        for (const op of level.operations) {
            // 分類項目の絞り込みは、配列形式+ExactMatchMultipleでないと完全一致にならない
            const view = {
                View: {
                    ColumnFilterHash: {
                        [level.linkColumn]: `["${currentParentId}"]`,
                    },
                    ColumnFilterSearchTypes: {
                        [level.linkColumn]: 'ExactMatchMultiple',
                    },
                },
            };
            const total = aggregate(
                op.aggregateType,
                siteId,
                op.sourceColumn,
                JSON.stringify(view),
            );
            if (total === null) {
                hasError = true;
                break;
            }
            parent[op.writeColumn] = total;
        }
        if (hasError) break;
        // 全operationsが完了したら親レコードを1回だけ更新
        parent.Update();
        // 次の階層へ: linkColumn を辿って親の親IDを取得
        currentParentId = parent[level.linkColumn];
    }
}
// ============================================================
// エントリポイント
// ============================================================
runHierarchySummary(HIERARCHY_CONFIG, model.ClassA);
2. 経費を申請するとき、部署の今月の申請額が予算を超えれば印を付けて知らせる

経費の申請を「申請中」にするとき、同じ部署・同じ月の申請中と承認済の金額をitems.Sumで合計し、今回の金額を足して予算を超えれば、「予算超過」のチェックを付けてメッセージを出します。申請は止めません。

実務での使いどころ

部署ごとに月の経費予算を決めている会社で、予算を超える申請を承認者がひと目で見分けられるようにしたいときに使えます。サマリは親のレコードへの集計で、申請するその時点で、ほかの申請との合計を予算と比べることはできません。

事前準備

1:「経費申請」テーブル(記録テーブル)で、以下の項目を有効化してください。

項目コード 内容
分類A(ClassA) 部署。選択肢一覧に部署を設定する([[Depts]]でもよい)
数値A(NumA) 金額
日付A(DateA) 利用日
チェックA(CheckA) 予算超過
状況(Status) 100(下書き)・200(申請中)・300(承認済)の選択肢を用意する

2:スクリプトを登録します。

2-1:「テーブルの管理 > サーバスクリプト」タブで「新規作成」を選択する。
 2-2:タイトル: 任意の名前(例:flagOverBudgetOnApply)
 2-3:スクリプト: 下記サンプルコードを貼り付け
 2-4:条件: 「更新前」にチェック

JavaScript

条件:更新前

// 「申請中」「承認済」の状況の値
const APPLIED_STATUS = 200;
const APPROVED_STATUS = 300;
// 部署ごとの月の予算(円)
const MONTHLY_BUDGET = 300000;

function toYmd(d) {
    const m = String(d.getMonth() + 1).padStart(2, '0');
    const day = String(d.getDate()).padStart(2, '0');
    return `${d.getFullYear()}/${m}/${day}`;
}

// 申請するとき、同じ部署・同じ月の申請額の合計に今回分を足し、予算を超えれば「予算超過」に印を付けて知らせる
function flagOverBudgetOnApply() {
    // 申請中「以外」から申請中「へ」変わる更新のときだけ見る(下書きの保存では見ない)
    const applying =
        Number(saved.Status) !== APPLIED_STATUS &&
        Number(model.Status) === APPLIED_STATUS;
    if (!applying || !model.ClassA || !model.DateA) {
        return;
    }
    // 利用日(日付A)の月の初日と末日
    const used = model.DateA;
    const first = new Date(used.getFullYear(), used.getMonth(), 1);
    const last = new Date(used.getFullYear(), used.getMonth() + 1, 0);
    const view = {
        View: {
            ColumnFilterHash: {
                ClassA: JSON.stringify([String(model.ClassA)]), // 部署(選択肢から選ぶ分類項目)
                Status: JSON.stringify([String(APPLIED_STATUS), String(APPROVED_STATUS)]),
                DateA: JSON.stringify([`${toYmd(first)} 00:00:00,${toYmd(last)} 23:59:59`]),
            },
        },
    };
    // 今回の申請はまだ下書きのまま保存されているので、合計に入らない。今回分を足す
    const total = items.Sum(context.SiteId, 'NumA', JSON.stringify(view)) + Number(model.NumA);
    model.CheckA = total > MONTHLY_BUDGET; // 予算超過
    if (model.CheckA) {
        context.AddMessage(
            `今月の部署の申請額が予算を超えます(今回を含めて${total.toLocaleString()}円、予算${MONTHLY_BUDGET.toLocaleString()}円)。` +
            '「予算超過」に印を付けました。',
            'alert-warning'
        );
    }
}

flagOverBudgetOnApply();

注記

  • 合計するのは、部署が同じで、状況が申請中か承認済、利用日が今回と同じ月の申請です。下書きの申請は合計しません。
  • 部署は、選択肢から選ぶ分類項目にしてください。自由に入力する分類項目では、部署の絞り込みが合いません。
  • 下書きで保存しても判定しません。下書きから申請中に変える更新のときに判定します。

関連情報