この記事をピアレビューするのを親切に手伝ってくれたJulian Motzに感謝します。
SQLデータベースとNoSQLデータベースの最大の違いの1つはJOINです。 リレーショナルデータベースでは、SQL JOIN句を使用すると、2つ以上のテーブルの行を、それらのテーブル間で共通のフィールドを使用して結合できます。 たとえば、books
およびpublishers
の表がある場合は、次のようなSQLコマンドを記述できます:
SELECT book.title, publisher.nameFROM bookLEFT JOIN book.publisher_id ON publisher.id;
つまり、book
テーブルにはpublisher_id
フィールドがあり、publisher
テーブルのid
フィールドを参照しています。
単一の出版社が何千冊もの本を提供できるので、これは実用的です。 パブリッシャーの詳細を更新する必要がある場合は、単一のレコードを変更できます。 すべての本について出版社の情報を繰り返す必要がないため、データの冗長性が最小限に抑えられます。 この技術は正規化として知られています。
SQLデータベースには、関係を維持するための一連の正規化機能と制約機能が用意されています。
NoSQL==結合しませんか?
必ずしも…
MongoDBのようなドキュメント指向のデータベースは、非正規化されたデータを格納するように設計されています。 理想的には、コレクション間には関係がないはずです。 複数の文書で同じデータが必要な場合は、繰り返す必要があります。
リレーショナルデータを必要としない状況がほとんどないため、これはイライラする可能性があります。 幸いなことに、MongoDB3.2では、2つ以上のコレクションに対して左外部結合のような操作を実行できる新しい$lookup
演算子が導入されています。 しかし、キャッチがあります…
MongoDB集約
$lookup
は集約操作でのみ許可されます。 これらは、結果を照会、フィルタリング、グループ化する演算子のパイプラインと考えてください。 ある演算子の出力は、次の演算子の入力として使用されます。
集約は、単純なfind
クエリよりも理解が難しく、一般的に実行が遅くなります。 しかし、それらは強力で、複雑な検索操作のための非常に貴重なオプションです。
集約は、例を使って説明するのが最善です。 user
コレクションを使用してソーシャルメディアプラットフォームを作成していると仮定します。 すべてのユーザーの詳細を別々の文書に保存します。 例えば:
{ "_id": ObjectID("45b83bda421238c76f5c1969"), "name": "User One", "email: "[email protected]", "country": "UK", "dob": ISODate("1999-09-13T00:00:00.000Z")}
必要な数のフィールドを追加できますが、すべてのMongoDBドキュメントには一意の値を持つ_id
フィールドが必要です。 _id
はSQLの主キーに似ており、必要に応じて自動的に挿入されます。
私たちのソーシャルネットワークには、ユーザーからの多数の洞察に満ちた更新を格納するpost
コレクションが必要です。 ドキュメントには、テキスト、日付、評価、およびそれを書いたユーザーへの参照がuser_id
フィールドに格納されます:
{ "_id": ObjectID("17c9812acff9ac0bba018cc1"), "user_id": ObjectID("45b83bda421238c76f5c1969"), "date: ISODate("2016-09-05T03:05:00.123Z"), "text": "My life story so far", "rating": "important"}
ここでは、すべてのユーザーからの「重要な」評価を逆の時系列で最後の20件の投稿を表示したいと考えています。 返される各文書には、テキスト、投稿の時刻、および関連するユーザーの名前と国が含まれている必要があります。
MongoDB集約クエリには、各操作を順番に定義するパイプライン演算子の配列が渡されます。 まず、$match
フィルタを使用して、正しい評価を持つpost
コレクションからすべてのドキュメントを抽出する必要があります:
{ "$match": { "rating": "important" } }
ここで、$sort
演算子を使用して、一致した項目を逆の日付順にソートする必要があります:
{ "$sort": { "date": -1 } }
必要なのは20個の投稿だけなので、$limit
ステージを適用することができるので、MongoDBは必要なデータのみを処理する必要があります:
{ "$limit": 20 }
新しい$lookup
演算子を使用して、user
コレクションのデータを結合できるようになりました。 これには、四つのパラメータを持つオブジェクトが必要です:
-
localField
: 入力文書内の参照項目 -
from
: 参加するコレクション -
foreignField
:from
コレクション内で検索するフィールド as
: 出力フィールドの名前。
:
{ "$lookup": { "localField": "user_id", "from": "user", "foreignField": "_id", "as": "userinfo"} }
これにより、userinfo
という名前の新しいフィールドが出力に作成されます。 これには、各値が一致するuser
ドキュメントである配列が含まれています:
"userinfo":
投稿には著者が一人しかいないため、post.user_id
とuser._id
の間には一対一の関係があります。 したがって、userinfo
配列には1つの項目のみが含まれます。 $unwind
演算子を使用して、それをサブドキュメントに分解することができます:
{ "$unwind": "$userinfo" }
出力は、さらに演算子を適用できるより実用的な形式に変換されます:
"userinfo": { "name": "User One", "email: "[email protected]", …}
最後に、パイプラインの$project
ステージを使用して、テキスト、投稿の時刻、ユーザーの名前、国を返すことができます:
{ "$project": { "text": 1, "date": 1, "userinfo.name": 1, "userinfo.country": 1} }
すべてをまとめる
最終的な集計クエリは、投稿と一致し、順序にソートし、最新の20項目に制限し、ユーザーデータを結合し、ユーザー配列をフラット化し、必要なフィー 完全なコマンド:
db.post.aggregate();
その結果、最大20件の文書が収集されます。 例えば:
素晴らしい! 私は最終的にNoSQLに切り替えることができます!
MongoDB$lookup
は便利で強力ですが、この基本的な例でさえ複雑な集約クエリが必要です。 これは、SQLで提供されるより強力なJOIN句の代替ではありません。 MongoDBはどちらも制約を提供しません。user
ドキュメントが削除された場合、孤立したpost
ドキュメントは残ります。
理想的には、$lookup
演算子は頻繁に必要ではありません。 たくさん必要な場合は、間違ったデータストアを使用している可能性があります…
リレーショナルデータをお持ちの場合は、リレーショナル(SQL)データベースを使用してください。
つまり、$lookup
はMongoDB3.2への歓迎された追加です。 NoSQLデータベースで少量のリレーショナルデータを使用する場合、よりイライラする問題のいくつかを克服することができます。