MongoDBで同等のSQL Joinを実行する方法を教えてください。
たとえば、2つのコレクション(ユーザーとコメント)があり、それぞれのユーザー情報と共にpid = 444ですべてのコメントを取得したいとします。
comments
{ uid:12345, pid:444, comment="blah" }
{ uid:12345, pid:888, comment="asdf" }
{ uid:99999, pid:444, comment="qwer" }
users
{ uid:12345, name:"john" }
{ uid:99999, name:"mia" }
特定のフィールド(例... find({pid:444}))と各コメントに関連付けられているユーザー情報をまとめてすべてのコメントを取得する方法はありますか?
現時点では、まず自分の基準に一致するコメントを取得し、次にその結果セット内のすべてのUIDを把握し、ユーザーオブジェクトを取得して、それらをコメントの結果とマージします。私はそれを間違ってやっているようです。
Mongo 3.2では、この質問に対する答えはほとんど正しくありません。集約パイプラインに追加された新しい$ lookup演算子は、基本的に左外部結合と同じです。
https://docs.mongodb.org/master/reference/operator/aggregation/lookup/#pipe._S_lookup
ドキュメントから:
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
もちろんMongoは not リレーショナルデータベースであり、開発者は$ lookupのための特定のユースケースを推奨するように注意していますが、少なくとも3.2以降ではMongoDBで結合が可能です。
公式のmongodbサイト上のこのページは 正確に この質問を扱っています:
http://docs.mongodb.org/ecosystem/tutorial/model-data-for-Ruby-on-Rails/ /
記事のリストを表示するときは、その記事を投稿したユーザーの名前を表示する必要があります。リレーショナルデータベースを使用している場合は、ユーザーとストアに対して結合を実行し、すべてのオブジェクトを単一のクエリで取得できます。しかし、MongoDBは結合をサポートしていないため、時には少しの非正規化が必要になります。ここで、これは 'username'属性をキャッシュすることを意味します。
リレーショナル純粋主義者は、あたかも私たちが何らかの普遍的な法律に違反しているかのように、すでに不安を感じているかもしれません。しかし、MongoDBのコレクションはリレーショナルテーブルと同等ではないことに注意してください。それぞれが独自の設計目的を果たします。正規化されたテーブルは、アトミックで独立したデータの塊を提供します。ただし、ドキュメントはオブジェクト全体をより厳密に表します。ソーシャルニュースサイトの場合、ユーザー名は投稿されるストーリーに固有のものであると主張できます。
Mongodbクライアントコンソールを使用して、簡単な機能で1つのコレクション内のすべてのデータを数行でマージ/結合できます。これで目的のクエリを実行できます。完全な例の下に、
- 著者:
db.authors.insert([
{
_id: 'a1',
name: { first: 'orlando', last: 'becerra' },
age: 27
},
{
_id: 'a2',
name: { first: 'mayra', last: 'sanchez' },
age: 21
}
]);
.-カテゴリ:
db.categories.insert([
{
_id: 'c1',
name: 'sci-fi'
},
{
_id: 'c2',
name: 'romance'
}
]);
.-本
db.books.insert([
{
_id: 'b1',
name: 'Groovy Book',
category: 'c1',
authors: ['a1']
},
{
_id: 'b2',
name: 'Java Book',
category: 'c2',
authors: ['a1','a2']
},
]);
本の貸出
db.lendings.insert([
{
_id: 'l1',
book: 'b1',
date: new Date('01/01/11'),
lendingBy: 'jose'
},
{
_id: 'l2',
book: 'b1',
date: new Date('02/02/12'),
lendingBy: 'maria'
}
]);
- マジック:
db.books.find().forEach(
function (newBook) {
newBook.category = db.categories.findOne( { "_id": newBook.category } );
newBook.lendings = db.lendings.find( { "book": newBook._id } ).toArray();
newBook.authors = db.authors.find( { "_id": { $in: newBook.authors } } ).toArray();
db.booksReloaded.insert(newBook);
}
);
新しいコレクションデータを取得します。
db.booksReloaded.find().pretty()
- レスポンス:)
{
"_id" : "b1",
"name" : "Groovy Book",
"category" : {
"_id" : "c1",
"name" : "sci-fi"
},
"authors" : [
{
"_id" : "a1",
"name" : {
"first" : "orlando",
"last" : "becerra"
},
"age" : 27
}
],
"lendings" : [
{
"_id" : "l1",
"book" : "b1",
"date" : ISODate("2011-01-01T00:00:00Z"),
"lendingBy" : "jose"
},
{
"_id" : "l2",
"book" : "b1",
"date" : ISODate("2012-02-02T00:00:00Z"),
"lendingBy" : "maria"
}
]
}
{
"_id" : "b2",
"name" : "Java Book",
"category" : {
"_id" : "c2",
"name" : "romance"
},
"authors" : [
{
"_id" : "a1",
"name" : {
"first" : "orlando",
"last" : "becerra"
},
"age" : 27
},
{
"_id" : "a2",
"name" : {
"first" : "mayra",
"last" : "sanchez"
},
"age" : 21
}
],
"lendings" : [ ]
}
私はこの行があなたを助けることができると思います。
あなたはそれをあなたが説明したようにしなければなりません。 MongoDBは非リレーショナルデータベースであり、結合をサポートしません。
これは"join" * 俳優 と 映画 コレクションの例です:
https://github.com/mongodb/cookbook/blob/master/content/patterns/pivot.txt
.mapReduce()
メソッドを利用しています
* join - ドキュメント指向データベースに参加する代わりの方法
他の人が指摘したように、あなたが本当にやりたくないリレーショナルデータベースからリレーショナルデータベースを作成しようとしています。最初にコレクションA(またはあなたの場合はユーザー)に対してforeach検索を行い、次に各アイテムをオブジェクトとして取得し、次にobjectプロパティ(あなたの場合はuid)を使用して2番目のコレクション(あなたの場合はコメント)を検索します。それを見つけることができ、それから私たちは試合をし、それを使って何かを印刷したり何かをすることができます。これがあなたと幸運を助けてくれることを願っています:)
db.users.find().forEach(
function (object) {
var commonInBoth=db.comments.findOne({ "uid": object.uid} );
if (commonInBoth != null) {
printjson(commonInBoth) ;
printjson(object) ;
}else {
// did not match so we don't care in this case
}
});
それはあなたがやろうとしていることによります。
現在、正規化されたデータベースとして設定されていますが、これは問題ありません。また、実行方法も適切です。
しかし、それを実行する方法は他にもあります。
あなたが取得するために繰り返し照会することができるユーザーへの参照を持つ各投稿へのコメントを埋め込んだ投稿コレクションを持つことができます。あなたはコメントと一緒にユーザーの名前を保存することができます、あなたはそれらすべてを1つの文書に保存することができます。
NoSQLのものは柔軟なスキーマと非常に速い読み書きのために設計されているということです。典型的なビッグデータファームでは、データベースが最大のボトルネックになります。データベースエンジンは、アプリケーションやフロントエンドサーバーよりも少なくなります。それらはより高価ですが、より強力で、ハードドライブスペースも比較的安価です。正規化は、スペースを節約しようとするという概念から来ていますが、データベースに複雑な結合を実行し、関係の整合性を検証し、カスケード操作を実行するというコストがかかります。これらはすべて、開発者がデータベースを適切に設計していれば、頭痛の種になります。
NoSQLを使用すると、冗長性とストレージスペースがコストのために問題にならないことに同意した場合(更新に必要なプロセッサ時間と余分なデータを格納するためのハードドライブコストの両方)、非正規化は問題になりません。何十万もの項目がパフォーマンスの問題になる可能性がありますが、ほとんどの場合それは問題ではありません。さらに、データベースクラスタごとに複数のアプリケーションサーバーとフロントエンドサーバーがあります。結合の負荷を取り除き、データベースサーバを読み書きに固執させます。
TL; DR:あなたがしていることは大丈夫です、そしてそれをする他の方法があります。 mongodbドキュメンテーションのデータモデルパターンを調べていくつかの良い例を見つけてください。 http://docs.mongodb.org/manual/data-modeling/ /
あなたは3.2バージョンで提供されているルックアップを使ってMongoの2つのコレクションに参加することができます。あなたの場合、クエリは次のようになります。
db.comments.aggregate({
$lookup:{
from:"users",
localField:"uid",
foreignField:"uid",
as:"users_comments"
}
})
または、ユーザーに関して参加することもできます。その場合は、次に示すように少し変更されます。
db.users.aggregate({
$lookup:{
from:"comments",
localField:"uid",
foreignField:"uid",
as:"users_comments"
}
})
SQLの左右結合とまったく同じように機能します。
DBRefと呼ばれる多くのドライバがサポートする仕様があります。
DBRefは文書間の参照を作成するためのより正式な仕様です。 DBRefは(一般的に)コレクション名とオブジェクトIDを含みます。ほとんどの開発者は、コレクションがあるドキュメントから次のドキュメントに変わる可能性がある場合にのみDBRefを使用します。参照コレクションが常に同じになる場合は、上記の手動参照の方が効率的です。
MongoDBのドキュメントからの抜粋: データモデル>データモデルリファレンス> データベースリファレンス
$ lookup 、 $ project 、および $ match を正しく組み合わせると、複数のパラメータで複数のテーブルを結合できます。これは、それらが複数回連鎖する可能性があるためです。
次のようにしたいとします( reference )
SELECT S.* FROM LeftTable S
LEFT JOIN RightTable R ON S.ID =R.ID AND S.MID =R.MID WHERE R.TIM >0 AND
S.MOB IS NOT NULL
ステップ1:すべてのテーブルをリンクする
あなたは望むだけの数のテーブルを$ lookupすることができます。
$ lookup - クエリ内の各テーブルに1つ
$ unwind - データは正しく非正規化され、それ以外の場合は配列にラップされるため
Pythonコード.
db.LeftTable.aggregate([
# connect all tables
{"$lookup": {
"from": "RightTable",
"localField": "ID",
"foreignField": "ID",
"as": "R"
}},
{"$unwind": "R"}
])
ステップ2:すべての条件を定義します
$ project :ここにすべての条件文と選択したいすべての変数を定義します。
Pythonコード..
db.LeftTable.aggregate([
# connect all tables
{"$lookup": {
"from": "RightTable",
"localField": "ID",
"foreignField": "ID",
"as": "R"
}},
{"$unwind": "R"},
# define conditionals + variables
{"$project": {
"midEq": {"$eq": ["$MID", "$R.MID"]},
"ID": 1, "MOB": 1, "MID": 1
}}
])
ステップ3:すべての条件式を結合する
$ match - ORまたはANDなどを使用してすべての条件に結合します。これらの倍数がある場合があります。
$ project :すべての条件文を未定義にする
Pythonコード..
db.LeftTable.aggregate([
# connect all tables
{"$lookup": {
"from": "RightTable",
"localField": "ID",
"foreignField": "ID",
"as": "R"
}},
{"$unwind": "$R"},
# define conditionals + variables
{"$project": {
"midEq": {"$eq": ["$MID", "$R.MID"]},
"ID": 1, "MOB": 1, "MID": 1
}},
# join all conditionals
{"$match": {
"$and": [
{"R.TIM": {"$gt": 0}},
{"MOB": {"$exists": True}},
{"midEq": {"$eq": True}}
]}},
# undefine conditionals
{"$project": {
"midEq": 0
}}
])
このようにして、テーブル、条件式、および結合のほとんどすべての組み合わせを実行できます。
同じデータベース内のシャーディングされていないコレクションへの左外部結合を実行して、処理のために「結合」コレクションからドキュメントをフィルタリングします。各入力文書に、$ lookupステージは、要素が「結合」コレクションからの一致する文書である新しい配列フィールドを追加します。 $ lookupステージはこれらの再整形された文書を次のステージに渡します。 $ lookupステージの構文は以下のとおりです。
入力文書のフィールドと「結合」コレクションの文書のフィールドとの間で等価一致を実行するための$ lookupステージの構文は以下のとおりです。
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
この操作は、次の疑似SQL文に対応します。
SELECT *, <output array field>
FROM collection
WHERE <output array field> IN (SELECT <documents as determined from the pipeline>
FROM <collection to join>
WHERE <pipeline> );
3.2.6 より前のバージョンでは、Mongodbはmysqlのように結合クエリをサポートしていません。あなたのために働くソリューションの下に。
db.getCollection('comments').aggregate([
{$match : {pid : 444}},
{$lookup: {from: "users",localField: "uid",foreignField: "uid",as: "userData"}},
])
Postgresの mongo_fdw を使用して、MongoDBでjoinを含むSQLクエリを実行できます。
MongoDBは結合を許可しませんが、それを処理するためにプラグインを使用することができます。 mongo-joinプラグインを確認してください。それは最高です、そして私はすでにそれを使いました。 npmを使って直接このnpm install mongo-join
のようにインストールできます。あなたは 完全なドキュメンテーションを例でチェックアウトすることができます 。
(N)コレクションを結合する必要があるとき(++)本当に便利なツール
( - )クエリの最上位レベルに条件を適用できます
例
var Join = require('mongo-join').Join, mongodb = require('mongodb'), Db = mongodb.Db, Server = mongodb.Server;
db.open(function (err, Database) {
Database.collection('Appoint', function (err, Appoints) {
/* we can put conditions just on the top level */
Appoints.find({_id_Doctor: id_doctor ,full_date :{ $gte: start_date },
full_date :{ $lte: end_date }}, function (err, cursor) {
var join = new Join(Database).on({
field: '_id_Doctor', // <- field in Appoints document
to: '_id', // <- field in User doc. treated as ObjectID automatically.
from: 'User' // <- collection name for User doc
}).on({
field: '_id_Patient', // <- field in Appoints doc
to: '_id', // <- field in User doc. treated as ObjectID automatically.
from: 'User' // <- collection name for User doc
})
join.toArray(cursor, function (err, joinedDocs) {
/* do what ever you want here */
/* you can fetch the table and apply your own conditions */
.....
.....
.....
resp.status(200);
resp.json({
"status": 200,
"message": "success",
"Appoints_Range": joinedDocs,
});
return resp;
});
});
集約パイプラインを使用してそれを実行できますが、それを自分で作成するのは面倒です。
mongo-join-query
を使用して、クエリから集計パイプラインを自動的に作成できます。
これはあなたのクエリがどのように見えるかです:
const mongoose = require("mongoose");
const joinQuery = require("mongo-join-query");
joinQuery(
mongoose.models.Comment,
{
find: { pid:444 },
populate: ["uid"]
},
(err, res) => (err ? console.log("Error:", err) : console.log("Success:", res.results))
);
あなたの結果はuid
フィールドにユーザオブジェクトがあるでしょう、そしてあなたはあなたが望むのと同じくらい多くのレベルをリンクすることができます。あなたはユーザへの参照を投入することができ、それはチームへの参照を作り、チームは他の何かへの参照を作ります。
免責事項 :私はこの問題に取り組むためにmongo-join-query
を書きました。
playORMはS-SQL(Scalable SQL)を使用してこれを実行できます。S-SQL(Scalable SQL)は、パーティション内で結合できるようにパーティション分割を追加するだけです。