MongoDBのaggregateの基礎

に公開

今回はMongoDBで集計する際に使用するaggregateに関して、基礎的なものをまとめておこうと思います。
※ Aggregate Piplineのみ

今回は以下のようなMLBの選手成績のデータを用意しました。
obpは"出塁率"、slgは"長打率"で
leagueに関してはALは"アメリカンリーグ"、NLは”ナショナルリーグ”になります。

[
  {
    "_id": 1,
    "name": "Ohtani",
    "obp": 412,
    "slg": 654,
    "league": "AL",
  },
  {
    "_id": 2,
    "name": "Yoshida",
    "obp": 338,
    "slg": 445,
    "league": "AL",
  },
  {
    "_id": 3,
    "name": "Betts",
    "obp": 408,
    "slg": 579,
    "league": "NL",
  },
  {
    "_id": 4,
    "name": "AcunaJr",
    "obp": 416,
    "slg": 596,
    "league": "NL",
  }
]


$addFields

addFieldsを使用することで、集計時にフィールドを追加することができます。
obp(出塁率)とslg(長打率)を足した値を新しく加えたopsというフィールドに出力したい場合は以下のようになります。
($addで配列の中の要素を足し合わせることができます)

db.collection.aggregate([
  {
    "$addFields": {
      "ops": {         
        $add: [
          "$obp",
          "$slg"
        ]
      }
    }
  }
])


出力結果:

[
  {
    "_id": 1,
    "league": "AL",
    "name": "Ohtani",
    "obp": 412,
    "ops": 1066,
    "slg": 654
  },
  {
    "_id": 2,
    "league": "AL",
    "name": "Yoshida",
    "obp": 338,
    "ops": 783,
    "slg": 445
  },
  {
    "_id": 3,
    "league": "NL",
    "name": "Betts",
    "obp": 408,
    "ops": 987,
    "slg": 579
  },
  {
    "_id": 4,
    "league": "NL",
    "name": "AcunaJr",
    "obp": 416,
    "ops": 1012,
    "slg": 596
  }
]


上記のようにopsが追加できました。

$match

$matchを使用することで、条件に合うものに絞り込むことができます。
今回は追加した先ほど追加したopsが.800を超える選手に絞り込むように$matchを使ってみます。

db.collection.aggregate([
  {
    "$addFields": {
      "ops": {
        $add: [
          "$obp",
          "$slg"
        ]
      }
    }
  },
   // 以下を追加
  {
    $match: {
      "ops": {
        $gt: 800
      }
    }
  }
])


出力結果:

[
  {
    "_id": 1,
    "league": "AL",
    "name": "Ohtani",
    "obp": 412,
    "ops": 1066,
    "slg": 654
  },
  {
    "_id": 3,
    "league": "NL",
    "name": "Betts",
    "obp": 408,
    "ops": 987,
    "slg": 579
  },
  {
    "_id": 4,
    "league": "NL",
    "name": "AcunaJr",
    "obp": 416,
    "ops": 1012,
    "slg": 596
  }
]


$matchを使用することで3選手に絞ることができました。

$project

$projectを使用することで残すフィールドや取り除くフィールドを設定することができます。
今回は先ほどの集計結果からnameとopsだけを残したものにして見ようと思います。
残したいフィールドには"1"を
取り除きたいフィールドには"0"を指定します。
何も設定しなければ取り除かれますが、"_id"に関しては削除したい場合は明示的に"0"を指定する必要があります。

db.collection.aggregate([
  {
    "$addFields": {
      "ops": {
        $add: [
          "$obp",
          "$slg"
        ]
      }
    }
  },
  {
    $match: {
      "ops": {
        $gt: 800
      }
    }
  },
   // 以下を追加
  {
    $project: {
      _id: 0,
      name: 1,
      ops: 1
    }
  }
])


出力結果:

[
  {
    "name": "Ohtani",
    "ops": 1066
  },
  {
    "name": "Betts",
    "ops": 987
  },
  {
    "name": "AcunaJr",
    "ops": 1012
  }
]


かなりスッキリした形になりました。

そして$projectは$addFieldsのように新しいフィールドの指定をすることで可能です。
以下のように指定します。

db.collection.aggregate([
  {
    $project: {
      _id: 0,
      name: 1,
      ops: {
        $add: [
          "$obp",
          "$slg"
        ]
      }
    }
  }
])


出力結果:

[
  {
    "name": "Ohtani",
    "ops": 1066
  },
  {
    "name": "Yoshida",
    "ops": 783
  },
  {
    "name": "Betts",
    "ops": 987
  },
  {
    "name": "AcunaJr",
    "ops": 1012
  }
]


今回は $matchを使用していないのでyoshidaも残っています

最後にリーグ別にopsの高い順に並び替えたいと思います。

$facet

$facetを使用することで複数の集計結果を出すことができます。

db.collection.aggregate([
  {
    $project: {
      _id: 0,
      name: 1,
      league: 1,
      ops: {
        $add: [
          "$obp",
          "$slg"
        ]
      }
    }
  },
   // 以下を追加
  {
    "$facet": {
      "NL": [
        {
          $match: {
            "league": "NL"
          }
        }
      ],
      "AL": [
        {
          $match: {
            "league": "AL"
          }
        }
      ],
    }
  }
])


出力結果:

[
  {
    "AL": [
      {
        "league": "AL",
        "name": "Ohtani",
        "ops": 1066
      },
      {
        "league": "AL",
        "name": "Yoshida",
        "ops": 783
      }
    ],
    "NL": [
      {
        "league": "NL",
        "name": "Betts",
        "ops": 987
      },
      {
        "league": "NL",
        "name": "AcunaJr",
        "ops": 1012
      }
    ]
  }
]


4選手をNLとALで分けることができました。

$sort

次に$sortを使用してopsの高い順に並び替えたいと思います。
$sortに1を指定することで昇順
-1を指定することで降順並び替えることができます。

db.collection.aggregate([
  {
    $project: {
      _id: 0,
      name: 1,
      league: 1,
      ops: {
        $add: [
          "$obp",
          "$slg"
        ]
      }
    }
  },
  {
    "$facet": {
      "NL": [
        {
          $match: {
            "league": "NL"
          }
        },
           // $sortを追加
        {
          "$sort": {
            "ops": -1
          }
        }
      ],
      "AL": [
        {
          $match: {
            "league": "AL"
          }
        },
           // $sortを追加
        {
          "$sort": {
            "ops": -1
          }
        }
      ]
    }
  }
])


出力結果:

[
  {
    "AL": [
      {
        "league": "AL",
        "name": "Ohtani",
        "ops": 1066
      },
      {
        "league": "AL",
        "name": "Yoshida",
        "ops": 783
      }
    ],
    "NL": [
      {
        "league": "NL",
        "name": "AcunaJr",
        "ops": 1012
      },
      {
        "league": "NL",
        "name": "Betts",
        "ops": 987
      }
    ]
  }
]


上記のようにALとNLのリーグごとのOPSの高い順に情報を集計することができました。