
GlideAggregateで件数を正しく数える ―groupByと画面フィルタの持ち込み
開発ハンズオンシリーズの2回目です。前回の「はじめてのGlideRecord」では、検索して・読んで・更新するまでを実機で動かしました。その終わりのほうで「件数を数えるなら getRowCount」と書きましたが、実はあの数え方には弱点があります。今回は、その弱点を解決する専用の道具 GlideAggregate(グライドアグリゲート) を、前回と同じ Scripts - Background で動かしていきましょう。
やることは「数える」だけ。ただし、全体の件数を出すだけでなく、優先度ごと・分類ごとに数えるところまで踏み込みます。SQL を触ったことがある方なら「GROUP BY をコードで書く」と言えば伝わります。さらに、一覧画面で作った絞り込みを、そのままコードに貼って使う小技(addEncodedQuery)も紹介します。前回のコードが読めた方なら、今回もすんなり入れるはずです。
この記事は、ServiceNow の PDI(Personal Developer Instance、無料の個人検証環境)Zurich リリース・OOTB(初期設定)で実機実行した記録です。コードの出力はすべて Scripts - Background で流した結果で、件数は REST でも二重確認しました(確認日 2026-07-19)。本番環境では実行しないでください。
なぜ「数える」なら GlideAggregate なのか
前回の数え方を思い出します。GlideRecord で query して、getRowCount() を読む。これでも件数は出ますね。ただ、この方法には弱点があります。該当する行をいったん全部サーバに取ってきてから数えている、という点です。数十件なら一瞬ですが、何万件もあるテーブルだと、その「全部取ってくる」がそのまま重さに直結します。
GlideAggregate は、この無駄をなくす道具です。件数や合計を データベース側で計算させて、答えの数字だけを受け取る。行そのものは持ち帰りません。図にすると、こういう違いになります。

上が前回の GlideRecord + getRowCount、下が今回の GlideAggregate + COUNT。結果はどちらも同じ数字ですが、途中でやっていることが違います。件数が増えるほど、この差は効いてきます。「1件ずつ中身を触りたい」なら GlideRecord、「数えたい・集計したい」なら GlideAggregate。この使い分けが今回の軸になります。
集計は、思っている以上にあちこちで顔を出します。ダッシュボードの「未解決件数」の数字、レポートの棒グラフ、Business Rule の中で「同じ担当グループに未処理が何件たまっているか」を数える処理。こうした「数える」の裏側は、たいてい GlideAggregate です。だから、ここでいちばん基本の形を押さえておくと、あとでレポートや自動化を触ったときに「ああ、これは中で集計が走っているんだな」と中身を想像できるようになります。表に見える数字の出どころが分かる、というのは意外と強い武器です。
もう少し具体的にしてみましょう。仮に incident が10万件あるインスタンスで「未解決が何件か」を知りたいとします。GlideRecord + getRowCount だと、条件に合う数万件をいったんメモリに展開してから数える動きになります。GlideAggregate なら、その展開をせずにデータベースへ「数だけ教えて」と頼む。前者はデータ量に比例して重くなり、後者はほぼ一定です。件数が小さいうちは体感差がなくても、データが育つほど後者が効いてくる。ここが「数えるなら GlideAggregate」と言われる理由です。
もちろん、今回の PDI は71件しかないので、体感ではどちらも一瞬です。ただ、書き方のクセは件数が少ないうちから整えておくに越したことはありません。小さいうちから正しい道具を選ぶ習慣が、データが増えたときの自分を助けてくれます。
総件数を数える:COUNT
まずは全体の件数から。GlideAggregate の基本形はこうです。
var ga = new GlideAggregate('incident');
ga.addAggregate('COUNT');
ga.query();
if (ga.next()) {
gs.info('incident の総件数: ' + ga.getAggregate('COUNT'));
}Scripts - Background に貼って実行すると、こうなります。

筆者の PDI では 71件 でした。流れは GlideRecord とよく似ています。new GlideAggregate('incident') で対象テーブルを決め、addAggregate('COUNT') で「件数を数えたい」と宣言し、query() で実行する。結果は1行だけ返るので、next() で1回進めて getAggregate('COUNT') で数字を取り出す、という流れです。
前回の getRowCount と同じ 71 が出ました。違うのは、この 71 を得るために71行を持ち帰っていない点です。見た目のコードはほとんど変わらないのに、裏の効率が違う。まずはこの「COUNT で総数」を基本形として覚えましょう。
ひとつ小さな注意を添えておきます。getAggregate('COUNT') が返すのは、数値ではなく文字列の "71" です。ログに出すだけならそのままで問題ありませんが、あとで足し算や比較に使うなら parseInt() で数値に直しておくと安全です。GlideRecord の getValue が文字列を返したのと同じ感覚ですね。
なお、if (ga.next()) で1回だけ進めているのは、COUNT だけを指定して groupBy を付けていないと、結果が1行しか返らないからです。次の groupBy を付けた例では、ここが while に変わります。同じ next でも、1行なら if、複数行なら while と使い分ける。この呼吸は、前回の GlideRecord と同じです。
分類ごとに数える:groupBy
GlideAggregate の本領は、ここからです。「優先度ごとに何件あるか」を、1回の集計でまとめて出せます。
var ga = new GlideAggregate('incident');
ga.addAggregate('COUNT');
ga.groupBy('priority');
ga.orderBy('priority');
ga.query();
while (ga.next()) {
gs.info('優先度 ' + ga.getValue('priority') + ' : ' + ga.getAggregate('COUNT') + ' 件');
}
結果は「優先度1が28件・2が4件・3が13件・4が5件・5が21件」。合計するとちゃんと71件になります。ここで効いているのが groupBy('priority') です。これは SQL の GROUP BY と同じで、「priority の値ごとにレコードをまとめて、それぞれ数えてね」という指示になります。
イメージとしては、全71件をいったん優先度別の山に分けて、山ごとに枚数を数える感じです。

groupBy を入れると、結果は「分類ごとに1行」返ります。だから while (ga.next()) で回します。1行ごとに getValue('priority')(どの山か)と getAggregate('COUNT')(その山の枚数)を読み出す形になります。前回の GlideRecord のループとそっくりですね。違いは、1行が「1レコード」ではなく「1グループ」を表している点だけです。
ちなみに getValue('priority') が返すのは、前回も出てきた生値(1〜5の数字)です。画面に出る「1 - Critical」のような表示にしたいなら、GlideRecord のときと同じく表示値に変換します。また groupBy は1つに限りません。groupBy('priority') に続けて groupBy('category') と重ねれば、「優先度×分類」のクロス集計もできます。まずは1項目の groupBy に慣れて、必要になったら項目を足す、という順番がおすすめです。
並び順にも一言だけ。上のコードでは orderBy('priority') を付けて、優先度の小さい順(1→5)にそろえました。これを外すと、並びはインスタンス任せになります。集計結果を人が読む前提なら、優先度順・件数順など、意味のある並びを指定しておくと親切です。次の例では、件数そのもので並べる orderByAggregate を使ってみます。
未解決だけ・多い順に:条件つきの集計
集計にも条件を足せます。GlideRecord と同じ addQuery がそのまま使えます。「未解決(active)のインシデントを、分類(category)ごとに、多い順で数える」を書いてみましょう。
var ga = new GlideAggregate('incident');
ga.addQuery('active', true);
ga.addAggregate('COUNT');
ga.groupBy('category');
ga.orderByAggregate('COUNT');
ga.query();
while (ga.next()) {
gs.info(ga.getValue('category') + ' : ' + ga.getAggregate('COUNT') + ' 件');
}
出力は「inquiry:18 / software:7 / hardware:5 /(空欄):4 / network:4 / database:1」の6グループ。合計39件で、これは未解決の総数と一致します。addQuery('active', true) で未解決だけに絞ってから、groupBy('category') で分類別に数えました。並び順に使ったのは orderByAggregate('COUNT')、つまり集計した件数そのものです。実機では件数の多い順に並びました。
面白いのは、category が空欄の行がひとつあることです。分類を入れずに起票されたインシデントが4件あり、それが「空のグループ」としてまとまっています。集計は、値が入っていないものも「値なし」というひとまとまりとして数える。ここは実務でも見落としやすいところで、「分類別の件数を足しても総数に合わない」と悩んだら、たいてい犯人はこの空グループです。実データを触ると、こうした素の姿がそのまま見えてきます。
こういう「空グループ」が見えるのも、実データで動かす価値のひとつです。ドキュメントを読むだけでは、分類が空のレコードが4件ある、なんてことは分かりません。実際に集計して初めて、そのインスタンスの素の状態が数字で立ち上がってきます。あなたの PDI で同じコードを流せば、また違う内訳になるはずです。その違いを眺めるのも、手を動かす学びの楽しさですね。
画面で作った条件をそのまま持ち込む:addEncodedQuery
最後に、実務でとても便利な小技を紹介しましょう。ServiceNow の一覧画面で絞り込みフィルタを組むと、その条件は裏で1本の文字列(エンコードされたクエリ)になっています。それを右クリックの「Copy query」で取り出せば、コードにそのまま貼れます。
var ga = new GlideAggregate('incident');
ga.addEncodedQuery('active=true^priorityIN1,2'); // Critical と High だけ
ga.addAggregate('COUNT');
ga.groupBy('priority');
ga.orderBy('priority');
ga.query();
while (ga.next()) {
gs.info('優先度 ' + ga.getValue('priority') + ' : ' + ga.getAggregate('COUNT') + ' 件');
}
優先度1が16件、2が4件と出ました。ポイントは addEncodedQuery('active=true^priorityIN1,2') の1行です。この文字列は、前回のように addQuery を2本積むのと同じ条件を、ひとつの文字列で表しています。読み方はシンプルで、^ が「かつ(AND)」、IN が「いずれか」。つまり「active が true、かつ priority が 1 か 2」という意味になります。
このやり方の何がうれしいかというと、画面でポチポチ組んだ複雑な条件を、手でコードに書き直さなくていいことです。フィルタを作って動きを確かめ、そのまま Copy query でコピーして addEncodedQuery に貼る。条件が長くても間違えにくく、確認も速い。実務では addQuery を一つずつ書くより、この持ち込みのほうが重宝する場面が多いです。
とくに、条件が5つも6つも重なる複雑な絞り込みでは、手書きのミスが起きやすいものです。画面で組めば動作をその場で確かめられますし、Copy query した文字列は、それ自体がすでに正解になっています。「画面で組んで、コピーして、貼る」。この流れは、覚えておいて損はありません。
エンコードクエリの読み方
addEncodedQuery に渡す文字列は、慣れると自分でも読めるようになります。よく使う記号だけ挙げておきます。
^ … かつ(AND)。例 active=true^priority=1
^OR … または(OR)。例 priority=1^ORpriority=2
IN … いずれか。例 priorityIN1,2(1 か 2)
!= … ではない。例 state!=7
LIKE … 部分一致。例 short_descriptionLIKEemail
ISEMPTY / ISNOTEMPTY … 空 / 空でない
ORDERBY / ORDERBYDESC … 並べ替え
とはいえ、複雑な条件を頭から手書きするより、画面のフィルタで組んで Copy query するほうが速くて確実です。記号は「読めれば十分」くらいの気持ちで大丈夫。書きたくなったときに、この表に戻ってくれば足ります。
ちなみに、GROUPBYcategory のように GROUPBY を条件文字列へ入れると、画面のリストでも分類ごとのグルーピング表示になります。コードの groupBy と、画面のグルーピングは地続きです。片方を覚えると、もう片方も自然と読めるようになりますよ。
SUM や AVG もあるけれど
GlideAggregate は COUNT だけでなく、数値項目の SUM(合計)・AVG(平均)・MIN・MAX も扱えます。ただ、これらには少し癖があります。実際に「担当替え回数の合計」を groupBy なしで出そうとしたところ、単一の合計にはならず、値ごとに分かれた複数行が返ってきました。数値集計は、指定のしかたによっては暗黙のグループ分けが起きることがあるのです。
この挙動は最初にハマりやすいので、SUM・AVG は回を改めてていねいに扱います。今回覚えてほしいのは、「数えるなら COUNT、分類するなら groupBy」という、いちばん出番の多い組み合わせです。まずはここを確実に手になじませておけば、集計の8割はこなせます。SUM や AVG は、その土台ができてからで十分間に合います。
補足すると、COUNT は「行が何本あるか」を数えるだけなので、こうした癖とは無縁です。だからこそ、最初に触るのに向いています。まずは COUNT で集計の型に慣れ、数値の合計や平均は次のステップ、と割り切って進めるのがおすすめです。
GlideRecord と GlideAggregate、どちらを使うか
前回の GlideRecord と今回の GlideAggregate。迷ったときの選び方を整理しておきます。

分かれ目は「中身を触りたいか、数えたいか」です。レコードの項目を読んだり書いたりするなら GlideRecord。件数や集計がほしいだけなら GlideAggregate。とくに「何件あるか」を知りたいだけなのに、GlideRecord で全行をぐるぐる回している、というのは、件数が増えたときにもったいない書き方になります。数えたいときは GlideAggregate、と反射で手が伸びるようになると、書くコードが一段軽くなります。
もちろん、両方を組み合わせる場面もあります。たとえば「未解決が5件以上たまっている担当グループを見つけて、その担当者に通知する」というケース。ここでは、まず GlideAggregate で担当グループごとの件数を数えます。そのうえで、条件に合うグループだけを GlideRecord でたどって、通知処理をかける。数えるところと触るところで、道具を役割分担させるわけです。この二段構えを頭に置いておくと、実務のスクリプトがぐっとすっきりします。現場のコードも、この「まず数えて、絞ってから触る」の形をよく取ります。
よく使うメソッド早見表
今回登場した GlideAggregate のメソッドをまとめておきます。前回の GlideRecord と見比べると、query や next はそのまま、集計まわりが増えた形だと分かります。

まとめ
GlideAggregate は、数えること・集計することに特化した GlideRecord の兄弟でした。総件数は addAggregate('COUNT')、分類ごとは groupBy、並べ替えは orderByAggregate。条件は addQuery でも、画面から持ち込む addEncodedQuery でも足せます。そして、行を持ち帰らずデータベース側で数えるので、件数が多いほど効いてきます。
振り返ると、今回のコードはどれも4〜7行の短いものでした。GlideAggregate は、身構えるほど難しくありません。COUNT と groupBy、この2枚看板さえ押さえておけば、日々の「これって何件あるんだろう?」の大半は、コードで答えを出せるようになります。
前回の GlideRecord と今回の GlideAggregate。この2つで「1件ずつ触る」「まとめて数える」の両方ができるようになりました。実務のスクリプトは、たいていこのどちらか、あるいは両方の組み合わせでできています。ぜひ自分の PDI で、優先度や分類を別の項目に変えて、いろいろ数えて遊んでみてください。手を動かした分だけ、集計の勘所が自分のものになっていきます。
次回は、ここまで Scripts - Background で試してきたコードを、Business Rule に載せて「保存した瞬間に自動で動く」ようにする話を予定しています。書いたクエリが、手で実行しなくても勝手に走り出す。自動化の第一歩へ進みます。
その他、実機で確認した記録
用語
GlideAggregate … 件数・合計などをDB側で集計する GlideRecord の兄弟API
addAggregate('COUNT') … 件数を数える指定(SUM/AVG/MIN/MAX もある)
groupBy(項目) … 項目ごとに分けて集計(SQL の GROUP BY)
orderByAggregate('COUNT') … 集計値で並べ替える
getAggregate('COUNT') … そのグループの集計値を読む(返るのは文字列)
addEncodedQuery(文字列) … 画面フィルタの条件文字列をそのまま適用(^=AND, IN=いずれか)
実行に使ったスクリプト
本文のコードは、そのまま Scripts - Background に貼れば動きます(実行は必ず PDI で)。出力はすべて筆者の PDI(dev / Zurich / OOTB)で流した実画面です。
調べるときの入口URL(先頭に自分のインスタンスを付ける)
/sys.scripts.do … Scripts - Background(今回コードを流した画面)
/incident_list.do?sysparm_query=active=true^priorityIN1,2 … addEncodedQuery と同じ条件を画面で確認
/incident_list.do?sysparm_query=active=true^GROUPBYcategory … 分類ごとの件数を画面のグルーピングで確認
/sys_dictionary_list.do?sysparm_query=name=incident^element=category … category 項目の定義
実測の要点(再掲)
総件数(COUNT)… 71件(REST の count でも 71 を確認)
優先度別(groupBy priority)… 1:28 / 2:4 / 3:13 / 4:5 / 5:21(合計71)
未解決を分類別(active + groupBy category)… inquiry:18 / software:7 / hardware:5
/(空):4 / network:4 / database:1(合計39)
addEncodedQuery(active=true^priorityIN1,2)… 1:16 / 2:4
検証範囲とおことわり
実行は PDI(Zurich / OOTB)の Scripts - Background・scope=global のみ。件数は執筆時点の値で、インスタンスによって変わります。
SUM/AVG/MIN/MAX の正しい単一集計や性能比較は本記事の範囲外です(暗黙グループ化の癖のみ実測で確認)。
orderByAggregate の昇順・降順の厳密仕様は、環境での実測(COUNT で多い順に並んだ)に留め、断定はしていません。