MongoDB で 指定した文字列を含む ドキュメント数 を 取得する

0 件のコメント

今回は「指定された文字列を含むドキュメント数を取得する方法」についてまとめます。 集計方法はいくつか考えられますが、ここではもっとも簡単な cout() を用いた方法、および、Aggregation を用いた方法についてまとめます。

サンプルとして「果物リストから "e" の文字列を含むドキュメント数を取得する方法」を例としてみていきます。

前提データ

前提データには以下のような「果物リスト」を準備します。

var CONNECTION_URL = "mongodb://localhost:27017/test";
var MongoClient = require("mongodb").MongoClient;

MongoClient.connect(CONNECTION_URL).then((db) => {
  db.collection("sample3").insertMany([
    { name: "melon", price: 12000 },
    { name: "watermelon", price: 3000 },
    { name: "apple", price: 200 },
    { name: "kiwi fruit", price: 120 },
    { name: "orange", price: 180 },
    { name: "banana", price: 300 }
  ], (result) => {
    db.close();
    console.log("OK");
  });
});

count() を用いた 単一フィールド の集計

もっとも簡単な集計方法は db.collection.count() を用いた集計でしょう。 ただ…次の「Aggregation を用いた 単一フィールド の集計」にも記載している通り、環境によっては不正確になる懸念があるので、実サービスでの利用はあまりオススメはできないかと思います。 あくまで実験用と思って利用するほうが良いでしょう。

サンプルコード

var CONNECTION_URL = "mongodb://localhost:27017/test";
var MongoClient = require("mongodb").MongoClient;

MongoClient.connect(CONNECTION_URL).then((db) => {
  db.collection("sample3")
    .find({ name: /e/g })
    .count((err, result) => {
      console.log(result);
    });
});

実行結果

> node .\index.js
4

Aggregation を用いた 単一フィールド の集計

シャードクラスタ環境下では count() が正しく動かないことがあるそうです。 こうした環境の場合、Aggregation を利用した以下のようなコードで正しい値を取得することができるそうです。

サンプルコード

MongoClient.connect(CONNECTION_URL).then((db) => {
  db.collection("sample3")
    .aggregate([
      { $match: { name: /e/g } },
      { $group: { _id: null, count: { $sum: 1 } } }
    ], (err, docs) => {
      console.log(docs[0].count);
    });
});

実行結果

> node .\index.js
4

Aggregation を用いた 複数フィールド の同時集計

例えば "e" を含むドキュメント数に加えて "l" を含むドキュメント数も同時に集計したい…といった場合に想定する集計方法です。 ここまでくるとなかなかに複雑ですが…1度で集計できてしまうのはやはり便利です。

サンプルコード

var CONNECTION_URL = "mongodb://localhost:27017/test";
var MongoClient = require("mongodb").MongoClient;

MongoClient.connect(CONNECTION_URL).then((db) => {
  db.collection("sample3")
    .aggregate([{
      $group: {
        _id: null,
        total: { $sum: 1 },
        count_e: { $sum: { $cond: { if: { $gte: [{ $indexOfCP: ["$name", "e"] }, 0] }, then: 1, else: 0 } } },
        count_l: { $sum: { $cond: { if: { $gte: [{ $indexOfCP: ["$name", "l"] }, 0] }, then: 1, else: 0 } } }
      }
    }, {
      $project: { _id: 0 }
    }], (err, docs) => {
      if (err) throw err;
      console.log(JSON.stringify(docs[0]));
    });
});

実行結果

> node .\index.js
{"total":6,"count_e":4,"count_l":3}

気にすべきオペレーターは $cond$gte$indexOfCP の3つでしょうか。 それぞれの引数と渡している値について見ていきます。

$cond{if: <boolean-expression>, then: <true-case-expression>, else: <false-case-expression>} を引数に取るオペレーターです。 今回のサンプルでは、条件文に「"e"を含む文字列かどうか」を示す構文を入れ、true の場合は "1" を返し、 false の場合は "0" を返すように実装しています。

「"e"を含むかどうか」を示す構文は $gte$indexOfCP を用いて記述します。

$gte は 長さ2の配列 [<target-value>, <basis-value>] を引数に取るオペレーターです。 今回のサンプルだと、第1引数に「指定された文字のインデックス(今回だと "e" のインデックス)」を指定し、それが第2引数に示す通り「0以上」であるかどうかを判定しています。

$indexOfCP は 長さ4の配列 [<target-string>, <search-string>, <start>, <end>] を引数に取るオペレーターです。 今回のサンプルでは、"$name" の文字列に含まれる "e" のインデックスを取得しています。 start と end は文字列全体を対象にするので無視しています。

これらを組み合わせると上記のサンプルコードのようになるのですが、なかなか複雑。。 ただ、この実装方法を使うと「対象を絞ってから集計(=あらかじめ $match を利用)」や「日付単位に集計(=$group_id に日付を指定)」などといった応用ができるようになります。 やりたいことに応じて実装方法を使い分けるのがベストかと思います。

今回は「指定された文字列を含むドキュメント数を取得する方法」についてまとめました。 ポイントは以下の通りです。

  • 集計には Aggregation を使う!

参考になったでしょうか? 本記事がお役に立っていると嬉しいです!!

参考記事

最後に… このブログに興味を持っていただけた方は、 ぜひ 「Facebookページ に いいね!」または 「Twitter の フォロー」 お願いします!!