今回は「MongoDB における 集計 (Aggregation) の基本」についてまとめます。 あまり細かいところは触れませんが、大まかな概略はつかめるようにまとめました。
集計手続き(Aggregation Pipeline)
一般に「集計を行う」となると「さまざまな処理を経て目的とする値を取得する」ようになっていると思います。
例えば、「今月売れた商品を売れた数順に取得する」のであれば「売上データを対象に今月の売り上げで絞り込み→商品ごとに売り上げ個数を集計→数が多い順にソート」といった流れで処理します。
これはRDBだと複数のサブクエリを作成して実現しているかと思いますが、
MongoDB だと aggregate([<stages>]) のように個別処理(ステージ)を配列にして aggregate() へ渡すことで実現します。
前述のサンプル(「今月売れた商品を売れた数順に取得する」)を実際に実行するコードと動作イメージは以下のようになります。
db.orderes.aggregate([
{ $match: { datetime: /2017-12/g } },
{ $group: { _id: "$item", total: { $sum: "$amount" } } }
{ $sort: { total: -1 } }
]);
MongoDB における統計処理は上記の通り「ステージ」を連結して必要な情報を集めることで実現します(上記の例だと「ステージ」は $match, $group, $sort)。 また、集計する際の各「ステージ」ではより複雑な処理を行うため、子要素に「オペレーター」と呼ばれる補助関数を使用することができます(上記の例だと「オペレーター」は $sum)。
以下では、よく使いそうな「ステージ」と「オペレーター」をピックアップして詳しく見ていきます。
ステージ
さて、上記の通り MongoDB における集計処理の要は「ステージ」にあります。 どのような「ステージ」があるかを知っておくことで様々な集計ができるようになります。
ここでは MongoDB で利用可能な ステージ のうち利用頻度が高そうなものをピックアップして紹介します。
ステージの記述方法は基本的に { <stage> : { <expression> } } というようにオブジェクトを引数に取るような形で記述します。
| 名前 | 説明 / サンプル |
|---|---|
| $count | 現在のステージに含まれるドキュメント数を指定したプロパティ名へ設定します。
{ $count: "total" }
|
| $group | 指定されたキー(_id)でグループ化を行います。 通常、統計関数をあわせて利用します。
{
$group: {
_id: "$item",
total: { $sum: "$amount" }
}
}
|
| $limit | 指定された件数までとなるようドキュメントを絞り込みます。
{ $limit: 15 }
|
| $skip | 先頭から指定された件数分のドキュメントを除外します。
{ $skip: 5 }
|
| $sort | 指定されたキーでソートします。 1 は 昇順、-1 は降順。
{ $sort: { total: -1 } }
|
| $match | 指定された条件に合致するドキュメントのみとなるよう絞り込みます。
{ $match: { datetime: /2017-12/g } }
|
| $project | 返却するドキュメントを再フォーマットします。
{
$project: {
_id: 0, item: 1, amount: 1
}
}
|
オペレーター
その名の通り各種演算です。 各ステージで行う集計処理でより複雑な処理を行いたいときに利用します。 SQL と同じく1つの言語 (MongoDB 処理用の言語) と思ってみたほうが良い気がします。
オペレーターの記述方法は基本的に { <operator> : [ <arguments> ] } というように配列で引数を取るような形で記述します。
ステージを含めた記述だと { <stage> : { <operator> : [ <arguments> ] } } という形になります。
オペレーターの引数として渡すドキュメントのプロパティはプロパティ名の接頭辞に $ つけて指定します。
関係演算子
| 名前 | 説明 / サンプル |
|---|---|
| $eq | 等値。 { $eq: [ "$amount", 250 ] } |
| $ne | 非等値。 { $ne: [ "$amount", 250 ] } |
| $gt | より大きい。 { $gt: [ "$amount", 250 ] } |
| $gte | より大きいか等しい。 { $gte: [ "$amount", 250 ] } |
| $lt | より小さい。 { $lt: [ "$amount", 250 ] } |
| $lte | より小さいか等しい。 { $lte: [ "$amount", 250 ] } |
論理演算子
| 名前 | 説明 / サンプル |
|---|---|
| $and | 論理積演算。 { $and: [ { $gte: [ "$amount", 5 ] }, { $lt: [ "$amount", 10 ] } ] }
|
| $or | 論理和演算。 { $or: [ { $lt: [ "$amount", 5 ] }, { $gte: [ "$amount", 10 ] } ] }
|
| $not | 論理否定演算。 { $not: [ { $gt: [ "$qty", 250 ] } ] }
|
算術演算子
| 名前 | 説明 / サンプル |
|---|---|
| $add | 加算。 { $add: [ "$price", "$fee" ] }
|
| $subtract | 減算。 { $subtract: [ "$price", "$discount" ] }
|
| $multiply | 乗算。 { $multiply: [ "$price", "$quantity" ] }
|
| $divide | 除算。 { $divide: [ "$hours", 8 ] }
|
| $mod | 剰余演算。 { $mod: [ "$hours", "$tasks" ] }
|
| $ceil | 切り上げ。 { $ceil: "$value" }
|
| $floor | 切り捨て。 { $floor: "$value" }
|
| $pow | 指数演算。以下のサンプルは分散を求める計算。
{ $pow: [ { $stdDevPop: "$scores.score" }, 2 ] } }
|
| $sqrt | 平方根。以下のサンプルは2点間の距離を求める計算。
{
$sqrt: {
$add: [
{ $pow: [ { $subtract: [ "$p2.y", "$p1.y" ] }, 2 ] },
{ $pow: [ { $subtract: [ "$p2.x", "$p1.x" ] }, 2 ] }
]
}
}
|
| $abs | 絶対値。以下のサンプルは2値間の大きさを求める計算。
{ $abs: { $subtract: [ "$start", "$end" ] } }
|
統計関数
| 名前 | 説明 / サンプル |
|---|---|
| $min | 最小値。
{ $min: "$amount" }
|
| $max | 最大値。
{ $max: "$amount" }
|
| $avg | 平均値。
{ $avg: "$amount" }
|
| $sum | 合計値。
{ $sum: "$amount" }
|
| $stdDevPop | 母標準偏差。
{ $stdDevPop: "$score" }
|
日付
| 名前 | 説明 / サンプル |
|---|---|
| $year | 年。
{ $year: "$date" }
|
| $month | 月。 1 ~ 12 。
{ $month: "$date" }
|
| $dayOfMonth | 月の中の日付。 1 ~ 31。
{ $dayOfMonth: "$date" }
|
| $dayOfWeek | 曜日を示す値。 1(日曜) ~ 7(土曜)。
{ $dayOfWeek: "$date" }
|
| $hour | 時。 0 ~ 23 。
{ $hour: "$date" }
|
| $minute | 分。 0 ~ 59 。
{ $minute: "$date" }
|
| $second | 秒。 0 ~ 60 (うるう秒) 。
{ $second: "$date" }
|
文字列
| 名前 | 説明 / サンプル |
|---|---|
| $strLenBytes | 文字列の UTF-8 バイト長を取得します。
{ $strLenBytes: "description" }
|
| $indexOfBytes | 指定された文字列が現れる UTF-8 バイトインデックスを取得します。 見つからなかった場合は -1 が戻ります。
{ $indexOfBytes: [ "$item", "e" ] }
|
| $concat | 指定された文字列を結合します。
{ $concat: [ "$item", " - ", "$description" ] }
|
| $substrBytes | 文字列の一部を取得します。 指定された UTF-8 バイトインデックスから、指定されたバイト数分取得します。
{ $substrBytes: [ "$description", 0, 2 ] }
|
| $split | 文字列を指定された区切り文字で分割します。
{ $split: ["$city", ","] }
|
| $toLower | 小文字に変換します。
{ $toLower: "$description" }
|
| $toUpper | 大文字に変換します。
{ $toUpper: "$description" }
|
文
| 名前 | 説明 / サンプル |
|---|---|
| $cond | if 文。 オブジェクトも引数としてとれます。
{ $cond: { if: { $gte: [ "$amount", 10 ] }, then: 1, else: 0 } }
{ $cond: [ { $gte: [ "$amount", 10 ] }, 1, 0 ] }
|
| $switch | switch 文。 一連の論理式を評価して最初に true となる式を実行します。
{
$switch: {
branches: [
{ case: { $gt: [ "$amount", 0 ] }, then: "> 0" },
{ case: { $gt: [ "$amount", 5 ] }, then: "> 5" }
],
default: "Did not match"
}
}
|
今回は「MongoDB における 集計 (Aggregation) の基本」についてまとめました。 ポイントは「ステージ」と「オペレーター」にあります。 これらをどれだけ知っているかが MongoDB を使いこなせるかどうかにつながります。 独特な書き方ですが本記事を足掛かりに知識を深められればと思います。
本記事がお役に立っていると嬉しいです!!
参考記事
- MongoDB - Aggregation
- MongoDB - Aggregation Pipeline Quick Reference
- MongoDB - Aggregation Pipeline Stages
- MongoDB - Aggregation Pipeline Operators
最後に… このブログに興味を持っていただけた方は、 ぜひ 「Facebookページ に いいね!」または 「Twitter の フォロー」 お願いします!!
