MongoDB で自動増分シーケンスを作成する方法

今回は MongoDB で自動増分シーケンスを作成する方法について説明する。

MongoDB には基本的にシーケンスが存在しないため、別途コードを作成する必要がある。ここでは簡単な例として 2 つの方法を紹介する。

概要

MongoDB は基本的に多数のドキュメントを扱うため、任意のフィールドが自動的にインクリメントされることはない。デフォルトでは _idObjectId の一意キーが生成されるため、通常はそれを使用する。

とはいえ、システム上で独自の連番が必要になる場合もある。今回は独自の番号を発行する方法を 2 つ紹介する。

どちらの方法でも、次のようにドキュメントの userid にシーケンスを発行する例を作成する。

  • database: test
  • collection: users
  • document: {userid, name}

カウンターコレクションを使用する方法

{key: string, seq: number} 形式で counters コレクションを作成し、シーケンスが必要なドキュメントを登録するときに、この counters ドキュメントから最大値を取得して更新することで自動インクリメントを作成する方法である。

前提 counters コレクションを作成する。次の例は mongo コマンドで実行する。

> db.counters.createIndex({ key: 1 }, { unique: true })
> db.counters.insertOne({ key: "userid", seq: 0 })

作成結果は次のとおりである。

> db.counters.find({}, { _id: 0 })
{ "key" : "userid", "seq" : 0 }

ソース (index.js)

var MongoClient = require("mongodb").MongoClient;
 
const CONNECTION_STRING = "mongodb://localhost:27017/test";
 
// シーケンス用コレクションを利用してシーケンスを発行する。
var insertUser = function (userinfo, callback) {
  MongoClient.connect(CONNECTION_STRING).then((db) => {

    // カウンターコレクションからシーケンスを取得する。
    db.collection("counters").findOneAndUpdate(
      { key: "userid" },
      { $inc: { seq: 1 } },
      { upsert: true, returnOriginal: true },
      (err, doc) => {
        // シーケンス番号を設定する。
        userinfo.userid = doc.value.seq;
 
        // ドキュメントを登録する。
        db.collection("users").insertOne(userinfo, (err, res) => {
          callback && callback(err, res);
        });
      });
  }).catch((err) => {
    console.log(err.message);
  });
};
 
// 実際に登録する。
insertUser({
  name: "kimkc"
});
 
insertUser({
  name: "devkuma"
});

実行

> node ./index.js

結果

> db.users.find({},{_id:0})
{ "name" : "kimkc", "userid" : 0 }
{ "name" : "devkuma", "userid" : 1 }

ループ処理を使用する方法

シーケンス用コレクションを作成せず、現在登録されているデータから最大値を取得して次の番号を取得する。

ソース (index.js)

var MongoClient = require("mongodb").MongoClient;
 
const CONNECTION_STRING = "mongodb://localhost:27017/test";
 
// ループ処理を利用したシーケンス発行。
var insertUser = function (userinfo, callback) {
  MongoClient.connect(CONNECTION_STRING).then((db) => {
 
    // ループ処理。
    var loop = function () {
      db.collection("users")
        .find({}, { _id: 0, userid: 1 })
        .sort({ userid: -1 })
        .limit(1)
        .toArray((err, docs) => {
          if (err) {
            callback && callback(err, null);
            return;
          }
 
          // シーケンスを作成する。
          var seq = docs.length > 0 ? docs[0].userid + 1 : 0;
 
          // シーケンスを設定する。
          userinfo.userid = seq;
 
          // ドキュメントを登録する。
          db.collection("users").insertOne(userinfo, (err, res) => {
            // シーケンス番号が使用済みの場合は再帰実行する。
            if (err && err.code === 11000) {
              loop();
 
              return;
            }
            callback && callback(err, res);
          });
        });
    };
 
    // ループ処理の初回実行。
    loop();
  });
};
 
// 実際に登録する。
insertUser({
  name: "kimkc"
});
 
insertUser({
  name: "devkuma"
});

使用:

> node ./index.js

結果:

> db.users.find({}, { _id: 0 })
{ "name" : "kimkc", "userid" : 0 }
{ "name" : "devkuma", "userid" : 1 }

参考文書