286

(MongoDB で) 複数のコレクションのデータを 1 つのコレクションに結合するにはどうすればよいですか?

map-reduce を使用できますか?

私は初心者なので、いくつかの例をいただければ幸いです。

4

11 に答える 11

153

これをリアルタイムで行うことはできませんが、MongoDB 1.8+ map / reduceの「reduce」outオプションを使用して、map-reduceを複数回実行し、データをマージすることができます(http://www.mongodb.org/を参照)。 display / DOCS / MapReduce#MapReduce-Outputoptions)。両方のコレクションに、_idとして使用できるキーが必要です。

たとえば、usersコレクションとcommentsコレクションがあり、コメントごとにユーザーの人口統計情報を含む新しいコレクションが必要だとします。

usersコレクションに次のフィールドがあるとします。

  • _id
  • ファーストネーム
  • 苗字
  • 性別

そして、commentsコレクションには次のフィールドがあります。

  • _id
  • ユーザーID
  • コメント
  • 作成した

このmap/reduceを実行します。

var mapUsers, mapComments, reduce;
db.users_comments.remove();

// setup sample data - wouldn't actually use this in production
db.users.remove();
db.comments.remove();
db.users.save({firstName:"Rich",lastName:"S",gender:"M",country:"CA",age:"18"});
db.users.save({firstName:"Rob",lastName:"M",gender:"M",country:"US",age:"25"});
db.users.save({firstName:"Sarah",lastName:"T",gender:"F",country:"US",age:"13"});
var users = db.users.find();
db.comments.save({userId: users[0]._id, "comment": "Hey, what's up?", created: new ISODate()});
db.comments.save({userId: users[1]._id, "comment": "Not much", created: new ISODate()});
db.comments.save({userId: users[0]._id, "comment": "Cool", created: new ISODate()});
// end sample data setup

mapUsers = function() {
    var values = {
        country: this.country,
        gender: this.gender,
        age: this.age
    };
    emit(this._id, values);
};
mapComments = function() {
    var values = {
        commentId: this._id,
        comment: this.comment,
        created: this.created
    };
    emit(this.userId, values);
};
reduce = function(k, values) {
    var result = {}, commentFields = {
        "commentId": '', 
        "comment": '',
        "created": ''
    };
    values.forEach(function(value) {
        var field;
        if ("comment" in value) {
            if (!("comments" in result)) {
                result.comments = [];
            }
            result.comments.push(value);
        } else if ("comments" in value) {
            if (!("comments" in result)) {
                result.comments = [];
            }
            result.comments.push.apply(result.comments, value.comments);
        }
        for (field in value) {
            if (value.hasOwnProperty(field) && !(field in commentFields)) {
                result[field] = value[field];
            }
        }
    });
    return result;
};
db.users.mapReduce(mapUsers, reduce, {"out": {"reduce": "users_comments"}});
db.comments.mapReduce(mapComments, reduce, {"out": {"reduce": "users_comments"}});
db.users_comments.find().pretty(); // see the resulting collection

この時点でusers_comments、マージされたデータを含むという新しいコレクションが作成され、それを使用できるようになります。これらの縮小されたコレクションはすべて_id、マップ関数で出力したキーであり、すべての値はvalueキー内のサブオブジェクトです。値は、これらの縮小されたドキュメントの最上位にはありません。

これはやや単純な例です。削減されたコレクションを構築し続けたい限り、より多くのコレクションでこれを繰り返すことができます。また、プロセスでデータの要約と集計を行うこともできます。既存のフィールドを集約および保存するためのロジックがより複雑になるため、複数のreduce関数を定義する可能性があります。

また、ユーザーごとに1つのドキュメントがあり、そのユーザーのすべてのコメントが配列に含まれていることにも注意してください。1対多ではなく1対1の関係にあるデータをマージする場合、データはフラットになり、次のようなreduce関数を使用できます。

reduce = function(k, values) {
    var result = {};
    values.forEach(function(value) {
        var field;
        for (field in value) {
            if (value.hasOwnProperty(field)) {
                result[field] = value[field];
            }
        }
    });
    return result;
};

コレクションをフラット化してusers_commentsコメントごとに1つのドキュメントにする場合は、さらに次のコマンドを実行します。

var map, reduce;
map = function() {
    var debug = function(value) {
        var field;
        for (field in value) {
            print(field + ": " + value[field]);
        }
    };
    debug(this);
    var that = this;
    if ("comments" in this.value) {
        this.value.comments.forEach(function(value) {
            emit(value.commentId, {
                userId: that._id,
                country: that.value.country,
                age: that.value.age,
                comment: value.comment,
                created: value.created,
            });
        });
    }
};
reduce = function(k, values) {
    var result = {};
    values.forEach(function(value) {
        var field;
        for (field in value) {
            if (value.hasOwnProperty(field)) {
                result[field] = value[field];
            }
        }
    });
    return result;
};
db.users_comments.mapReduce(map, reduce, {"out": "comments_with_demographics"});

この手法は、その場で実行するべきではありません。cronジョブなど、マージされたデータを定期的に更新するものに適しています。新しいコレクションで実行ensureIndexして、それに対して実行するクエリが迅速に実行されるようにすることをお勧めします(データはまだキー内にあることに注意してください。したがって、コメント時間でvalueインデックスを作成する場合は、次のようになります。comments_with_demographicscreateddb.comments_with_demographics.ensureIndex({"value.created": 1});

于 2012-01-05T17:24:14.740 に答える
18

$lookup を使用した非常に基本的な例。

db.getCollection('users').aggregate([
    {
        $lookup: {
            from: "userinfo",
            localField: "userId",
            foreignField: "userId",
            as: "userInfoData"
        }
    },
    {
        $lookup: {
            from: "userrole",
            localField: "userId",
            foreignField: "userId",
            as: "userRoleData"
        }
    },
    { $unwind: { path: "$userInfoData", preserveNullAndEmptyArrays: true }},
    { $unwind: { path: "$userRoleData", preserveNullAndEmptyArrays: true }}
])

ここが使われています

 { $unwind: { path: "$userInfoData", preserveNullAndEmptyArrays: true }}, 
 { $unwind: { path: "$userRoleData", preserveNullAndEmptyArrays: true }}

それ以外の

{ $unwind:"$userRoleData"} 
{ $unwind:"$userRoleData"}

{ $unwind:"$userRoleData"}であるため、$lookup で一致するレコードが見つからない場合、これは空または 0 の結果を返します。

于 2016-10-24T08:15:43.517 に答える
14

mongodb への一括挿入がない場合は、 内のすべてのオブジェクトをループし、small_collection1 つずつ に挿入しますbig_collection

db.small_collection.find().forEach(function(obj){ 
   db.big_collection.insert(obj)
});
于 2014-09-12T09:06:39.950 に答える
13

から始めて、新しい集計ステージをの new演算子とMongo 4.4結合することにより、集計パイプライン内でこの結合を実現できます。$unionWith$group$accumulator

// > db.users.find()
//   [{ user: 1, name: "x" }, { user: 2, name: "y" }]
// > db.books.find()
//   [{ user: 1, book: "a" }, { user: 1, book: "b" }, { user: 2, book: "c" }]
// > db.movies.find()
//   [{ user: 1, movie: "g" }, { user: 2, movie: "h" }, { user: 2, movie: "i" }]
db.users.aggregate([
  { $unionWith: "books"  },
  { $unionWith: "movies" },
  { $group: {
    _id: "$user",
    user: {
      $accumulator: {
        accumulateArgs: ["$name", "$book", "$movie"],
        init: function() { return { books: [], movies: [] } },
        accumulate: function(user, name, book, movie) {
          if (name) user.name = name;
          if (book) user.books.push(book);
          if (movie) user.movies.push(movie);
          return user;
        },
        merge: function(userV1, userV2) {
          if (userV2.name) userV1.name = userV2.name;
          userV1.books.concat(userV2.books);
          userV1.movies.concat(userV2.movies);
          return userV1;
        },
        lang: "js"
      }
    }
  }}
])
// { _id: 1, user: { books: ["a", "b"], movies: ["g"], name: "x" } }
// { _id: 2, user: { books: ["c"], movies: ["h", "i"], name: "y" } }
  • $unionWithすでに集計パイプラインにあるドキュメント内の特定のコレクションからのレコードを結合します。2 つのユニオン ステージの後、パイプライン内にすべてのユーザー、本、および映画のレコードがあります。

  • 次に、オペレーターを使用してアイテムを$group記録および蓄積し、グループ化されたドキュメントのカスタム蓄積を可能にします。$user$accumulator

    • 蓄積したいフィールドは で定義されaccumulateArgsます。
    • init要素をグループ化するときに蓄積される状態を定義します。
    • このaccumulate関数を使用すると、レコードをグループ化してカスタム アクションを実行し、蓄積された状態を構築できます。たとえば、グループ化されているアイテムにbookフィールドが定義されている場合books、状態の一部を更新します。
    • merge2 つの内部状態をマージするために使用されます。これは、シャード クラスターで実行されている集計、または操作がメモリ制限を超えた場合にのみ使用されます。
于 2020-03-15T15:37:32.347 に答える
1

Mongorestore には、データベースに既に存在するものの上に追加する機能があるため、この動作を使用して 2 つのコレクションを組み合わせることができます。

  1. モンゴダンプコレクション1
  2. collection2.rename(コレクション1)
  3. モンゴレストア

まだ試していませんが、map/reduce アプローチよりも高速に実行される可能性があります。

于 2014-08-21T23:35:45.120 に答える
0

はい、できます: 今日私が書いた次のユーティリティ関数を使用してください。

function shangMergeCol() {
  tcol= db.getCollection(arguments[0]);
  for (var i=1; i<arguments.length; i++){
    scol= db.getCollection(arguments[i]);
    scol.find().forEach(
        function (d) {
            tcol.insert(d);
        }
    )
  }
}

この関数には任意の数のコレクションを渡すことができ、最初のコレクションがターゲットになります。残りのコレクションはすべて、ターゲット コレクションに転送されるソースです。

于 2015-06-07T23:29:27.670 に答える
-3

アプリケーション層でそれを行う必要があります。ORM を使用している場合は、注釈 (または類似のもの) を使用して、他のコレクションに存在する参照を取得できます。私はMorphiaのみを使用しており、@Reference注釈はクエリ時に参照エンティティをフェッチするため、コードで自分で行うことを避けることができます。

于 2011-04-15T23:39:12.317 に答える