JSONataで集計処理をやってみた

JSONataで集計処理をやってみた

Clock Icon2025.07.16

こんにちは。サービス開発室の武田です。

jqコマンドでgroup byやmaxなどの集計処理をやってみたという記事を見かけたので、練習がてら、同じことをJSONataでもやってみました。

参考にさせていただいたのはこちらのブログです。ありがとうございます!

https://www.karakaram.com/aggregating-with-jq-command/

また今回CLIで実行するにあたっては、拙作jtを使用しました。

https://github.com/TAKEDA-Takashi/jt-cli

AWS CLIと絡めてJSONata使ってみたのはこちら(jt-cli開発前なのでjfqを使っています)。

https://dev.classmethod.jp/articles/jsonata-on-the-command-line-tool-jfq/

サンプルのJSON

参考のブログと同じデータを使用します。同じ結果となるようにクエリを書いていきましょう。

JSON=$(cat << EOS
[
  {
    "user": "alice",
    "item": "apple",
    "price": 100,
    "created_at": "2021-12-25T17:00:00Z"
  },
  {
    "user": "bob",
    "item": "orange",
    "price": 150,
    "created_at": "2021-12-25T17:10:00Z"
  },
  {
    "user": "carol",
    "item": "apple",
    "price": 100,
    "created_at": "2021-12-25T17:20:00Z"
  },
  {
    "user": "alice",
    "item": "orange",
    "price": 150,
    "created_at": "2021-12-25T17:30:00Z"
  },
  {
    "user": "alice",
    "item": "banana",
    "price": 200,
    "created_at": "2021-12-25T17:40:00Z"
  },
  {
    "user": "carol",
    "item": "apple",
    "price": 100,
    "created_at": "2021-12-25T17:50:00Z"
  }
]
EOS
)

最大値

created_atが最大の値を取得します。ですが、実はJSONataにはjqのmax_byにあたる関数はありません。そのため、愚直に書くなら、次のようになります。

$ echo $JSON | jt '$reduce($, function($acc, $item) { $item.created_at > $acc.created_at ? $item : $acc })'
{
  "user": "carol",
  "item": "apple",
  "price": 100,
  "created_at": "2021-12-25T17:50:00Z"
}

計算量は増えますが、よりスマートに書くなら、次のような書き方もできます。created_atで降順ソートして最初の値を取り出しています。

echo $JSON | jt '$^(>created_at)[0]'

合計値

priceの合計を求めます。JSONataでは$sumで簡単です。

$echo $JSON | jt '$sum(price)'
800

配列の数

配列の要素数は$countを使えば簡単です。

$ echo $JSON | jt '$count($)'
6

グループごとの最大値

各ユーザーごとにcreated_atが最大の値を取得します。jqではgroup_bymax_byなど便利な関数が用意されていますが、JSONataでは標準で提供されていません。その代わりパスオペレーターなどを駆使することで同等のことが実現できます。

$ echo $JSON | jt '${user:$^(>created_at)[0]}.*'
[
  {
    "user": "alice",
    "item": "banana",
    "price": 200,
    "created_at": "2021-12-25T17:40:00Z"
  },
  {
    "user": "bob",
    "item": "orange",
    "price": 150,
    "created_at": "2021-12-25T17:10:00Z"
  },
  {
    "user": "carol",
    "item": "apple",
    "price": 100,
    "created_at": "2021-12-25T17:50:00Z"
  }
]

ややこしいので分解して見てみましょう。${}という記法は要素をグルーピングするものです(jqのgroup_byに近い)。userの値をキーにしてグルーピングします。

$ echo $JSON | jt '${user:$}'
{
  "alice": [
    {
      "user": "alice",
      "item": "apple",
      "price": 100,
      "created_at": "2021-12-25T17:00:00Z"
    },
    {
      "user": "alice",
      "item": "orange",
      "price": 150,
      "created_at": "2021-12-25T17:30:00Z"
    },
    {
      "user": "alice",
      "item": "banana",
      "price": 200,
      "created_at": "2021-12-25T17:40:00Z"
    }
  ],
  "bob": {
    "user": "bob",
    "item": "orange",
    "price": 150,
    "created_at": "2021-12-25T17:10:00Z"
  },
  "carol": [
    {
      "user": "carol",
      "item": "apple",
      "price": 100,
      "created_at": "2021-12-25T17:20:00Z"
    },
    {
      "user": "carol",
      "item": "apple",
      "price": 100,
      "created_at": "2021-12-25T17:50:00Z"
    }
  ]
}

各グループごとに最大の値が欲しいので、最初に使ったテクニックを使用します。

$ echo $JSON | jt '${user:$^(>created_at)[0]}'
{
  "alice": {
    "user": "alice",
    "item": "banana",
    "price": 200,
    "created_at": "2021-12-25T17:40:00Z"
  },
  "bob": {
    "user": "bob",
    "item": "orange",
    "price": 150,
    "created_at": "2021-12-25T17:10:00Z"
  },
  "carol": {
    "user": "carol",
    "item": "apple",
    "price": 100,
    "created_at": "2021-12-25T17:50:00Z"
  }
}

グループのキーが残っていますので、.*で取り除きます。

$ echo $JSON | jt '${user:$^(>created_at)[0]}.*'
[
  {
    "user": "alice",
    "item": "banana",
    "price": 200,
    "created_at": "2021-12-25T17:40:00Z"
  },
  {
    "user": "bob",
    "item": "orange",
    "price": 150,
    "created_at": "2021-12-25T17:10:00Z"
  },
  {
    "user": "carol",
    "item": "apple",
    "price": 100,
    "created_at": "2021-12-25T17:50:00Z"
  }
]

グループごとの合計値

ユーザーごとにpriceの合計を求めます。グループ処理ですので、基本テクニックは先ほどと同じです。

$ echo $JSON | jt '${user:$sum(price)}.$each(function($v, $k) {{"user": $k, "price": $v}})'
[
  {
    "user": "alice",
    "price": 450
  },
  {
    "user": "bob",
    "price": 150
  },
  {
    "user": "carol",
    "price": 200
  }
]

$eachが何をしているのか確認しておきましょう。これがないと次のようになります。

$ echo $JSON | jt '${user:$sum(price)}'
{
  "alice": 450,
  "bob": 150,
  "carol": 200
}

1オブジェクトの各キーと値のペアについて、{"user": key, "price": value}の形に整形できれば目的の形になります。$eachはまさしく各キーと値のペアに対して処理をする関数です。

ちなみにこの処理はいろいろな書き方ができそうで、次の2パターンを追加で考えてみました。

$ echo $JSON | jt '${user:$sum(price)}.$spread().{"user": $keys(), "price": *}'
$ echo $JSON | jt '${user:$sum(price)} ~> $each(function($v, $k) {{"user": $k, "price": $v}})'

グループごとの配列の数

ユーザーごとに要素数を求めます。ほぼ同じ書き方ができます。

$ echo $JSON | jt '${user:$count($)} ~> $each(function($v, $k) {{"user": $k, "length": $v}})'
[
  {
    "user": "alice",
    "length": 3
  },
  {
    "user": "bob",
    "length": 1
  },
  {
    "user": "carol",
    "length": 2
  }
]

グループごとの平均

ユーザーごとにpriceの平均を求めます。これも書き方はほぼ同じです。

$ echo $JSON | jt '${user:$average(price)} ~> $each(function($v, $k) {{"user": $k, "average": $v}})'
[
  {
    "user": "alice",
    "average": 150
  },
  {
    "user": "bob",
    "average": 150
  },
  {
    "user": "carol",
    "average": 100
  }
]

集計後の並び替え

priceの合計をユーザーごとに求め、その値でソートします。

$ echo $JSON | jt '${user:$sum(price)} ~> $each(function($v, $k) {{"user": $k, "price": $v}})^(price)'
[
  {
    "user": "bob",
    "price": 150
  },
  {
    "user": "carol",
    "price": 200
  },
  {
    "user": "alice",
    "price": 450
  }
]

絞り込んでから集計

フィルターを追加([created_at >= "2021-12-25T17:30:00Z"])するだけの簡単なお仕事です。

$ echo $JSON | jt '$[created_at >= "2021-12-25T17:30:00Z"]{user: $count($)} ~> $each(function($v, $k) {{"user": $k, "length": $v}})'
[
  {
    "user": "alice",
    "length": 2
  },
  {
    "user": "carol",
    "length": 1
  }
]

reduceを使った集計

最初に$reduce使っていますが、ここでは$sum相当の処理を$reduceで書いてみようということですね。

$ echo $JSON | jt '${user:$reduce(price, function($a, $p) { $a + $p })} ~> $each(function($v, $k) {{"user": $k, "price": $v}})'
[
  {
    "user": "alice",
    "price": 450
  },
  {
    "user": "bob",
    "price": 150
  },
  {
    "user": "carol",
    "price": 200
  }
]

まとめ

グルーピングやソート処理などを絡めたクエリを書く練習をしてみました。こういった処理をスムーズに書けると、集計処理も捗りますね!

ちなみにjt-cliでは入力がJSONだけでなくJSON LinesやCSVも対応していますので、きっと役立ってくれるはずです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.