JOINは、SQLデータベースとNoSQLデータベースの重要な特徴の1つです。 SQLデータベースでは、同じデータベースまたは異なるデータベース内の2つのテーブル間でJOINを実行できます。ただし、これはMongoDBには当てはまりません。これは、同じデータベース内の2つのコレクション間のJOIN操作を可能にするためです。
MongoDBでのデータの表示方法では、基本的なスクリプトクエリ関数を使用する場合を除いて、あるコレクションから別のコレクションにデータを関連付けることはほとんど不可能です。 MongoDBは、関連するアイテムを別のドキュメントに保存することでデータを非正規化するか、データを別の別のドキュメントに関連付けます。
別のドキュメントに参照として保存されている1つのドキュメントの_idフィールドなどの手動参照を使用して、このデータを関連付けることができます。それでも、必要なデータを取得するには複数のクエリを実行する必要があり、プロセスが少し面倒になります。
したがって、データの関係を容易にするJOINの概念を使用することを決意します。 MongoDBでのJOIN操作は、バージョン3.2で導入された$lookup演算子を使用して実現されます。
$lookup演算子
JOINの概念の背後にある主なアイデアは、あるコレクションのデータと別のコレクションのデータ間の相関関係を取得することです。 $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の知識に関しては、JOIN操作の結果が、ローカルテーブルと外部テーブルのすべてのフィールドをリンクする個別の行になることを常に知っています。 MongoDBの場合、これは、結果ドキュメントがローカルコレクションドキュメントの配列として追加されるという点で別のケースです。たとえば、2つのコレクションを作成しましょう。 「学生」と「ユニット」
学生
{"_id" : 1,"name" : "James Washington","age" : 15.0,"grade" : "A","score" : 10.5}
{"_id" : 2,"name" : "Clinton Ariango","age" : 14.0,"grade" : "B","score" : 7.5}
{"_id" : 3,"name" : "Mary Muthoni","age" : 16.0,"grade" : "A","score" : 11.5}
ユニット
{"_id" : 1,"Maths" : "A","English" : "A","Science" : "A","History" : "B"}
{"_id" : 2,"Maths" : "B","English" : "B","Science" : "A","History" : "B"}
{"_id" : 3,"Maths" : "A","English" : "A","Science" : "A","History" : "A"}
JOINアプローチで$lookup演算子を使用して、それぞれの成績の学生ユニットを取得できます。つまり
db.getCollection('students').aggregate([{
$lookup:
{
from: "units",
localField: "_id",
foreignField : "_id",
as: "studentUnits"
}
}])
以下の結果が得られます:
{"_id" : 1,"name" : "James Washington","age" : 15,"grade" : "A","score" : 10.5,
"studentUnits" : [{"_id" : 1,"Maths" : "A","English" : "A","Science" : "A","History" : "B"}]}
{"_id" : 2,"name" : "Clinton Ariango","age" : 14,"grade" : "B","score" : 7.5,
"studentUnits" : [{"_id" : 2,"Maths" : "B","English" : "B","Science" : "A","History" : "B"}]}
{"_id" : 3,"name" : "Mary Muthoni","age" : 16,"grade" : "A","score" : 11.5,
"studentUnits" : [{"_id" : 3,"Maths" : "A","English" : "A","Science" : "A","History" : "A"}]}
前述のように、SQLの概念を使用してJOINを実行すると、Studio3Tプラットフォームで個別のドキュメントが返されます。つまり
SELECT *
FROM students
INNER JOIN units
ON students._id = units._id
と同等です
db.getCollection("students").aggregate(
[
{
"$project" : {
"_id" : NumberInt(0),
"students" : "$$ROOT"
}
},
{
"$lookup" : {
"localField" : "students._id",
"from" : "units",
"foreignField" : "_id",
"as" : "units"
}
},
{
"$unwind" : {
"path" : "$units",
"preserveNullAndEmptyArrays" : false
}
}
]
);
上記のSQLクエリは、以下の結果を返します。
{ "students" : {"_id" : NumberInt(1),"name" : "James Washington","age" : 15.0,"grade" : "A","score" : 10.5},
"units" : {"_id" : NumberInt(1),"Maths" : "A","English" : "A","Science" : "A","History" : "B"}}
{ "students" : {"_id" : NumberInt(2), "name" : "Clinton Ariango","age" : 14.0,"grade" : "B","score" : 7.5 },
"units" : {"_id" : NumberInt(2),"Maths" : "B","English" : "B","Science" : "A","History" : "B"}}
{ "students" : {"_id" : NumberInt(3),"name" : "Mary Muthoni","age" : 16.0,"grade" : "A","score" : 11.5},
"units" : {"_id" : NumberInt(3),"Maths" : "A","English" : "A","Science" : "A","History" : "A"}}
パフォーマンスの持続時間は、明らかにクエリの構造に依存します。たとえば、あるコレクションに他のコレクションよりも多くのドキュメントがある場合は、コレクションからより少ないドキュメントで集計を実行してから、より多くのドキュメントでコレクションを検索する必要があります。このように、より少ないドキュメントコレクションから選択されたフィールドを検索することは非常に最適であり、より多くのドキュメントを含むコレクション内の選択されたフィールドを複数回検索するよりも時間がかかりません。したがって、小さいコレクションを最初に配置することをお勧めします。
リレーショナルデータベースの場合、ほとんどのSQLインタープリターにはオプティマイザーがあり、オプティマイザーはどちらを最初にするかを決定するための追加情報にアクセスできるため、データベースの順序は重要ではありません。
MongoDBの場合、JOIN操作を容易にするためにインデックスを使用する必要があります。すべてのMongoDBドキュメントに_idキーがあり、リレーショナルDBMの場合は主キーと見なすことができます。インデックスは、$ lookup外部キーで使用される場合の操作をサポートするだけでなく、アクセスする必要のあるデータの量を減らす可能性が高くなります。
集計パイプラインでは、インデックスを使用するために、条件に一致しないドキュメントを除外するために、$matchが最初の段階で実行されることを確認する必要があります。たとえば、_idフィールドの値が1に等しい学生の結果を取得する場合:
select *
from students
INNER JOIN units
ON students._id = units._id
WHERE students._id = 1;
この場合に取得する同等のMongoDBコードは次のとおりです。
db.getCollection("students").aggregate(
[{"$project" : { "_id" : NumberInt(0), "students" : "$$ROOT" }},
{ "$lookup" : {"localField" : "students._id", "from" : "units", "foreignField" : "_id", "as" : "units"} },
{ "$unwind" : { "path" : "$units","preserveNullAndEmptyArrays" : false } },
{ "$match" : {"students._id" : NumberLong(1) }}
]);
上記のクエリで返される結果は次のようになります。
{"_id" : 1,"name" : "James Washington","age" : 15,"grade" : "A","score" : 10.5,
"studentUnits" : [{"_id" : 1,"Maths" : "A","English" : "A","Science" : "A","History" : "B"}]}
$ matchステージを使用しない場合、または最初のステージでは使用しない場合、explain関数で確認すると、COLLSCANステージも含まれます。大量のドキュメントに対してCOLLSCANを実行すると、通常、多くの時間がかかります。これにより、explain関数でIXSCANステージのみを含むインデックスフィールドを使用することにしました。後者には、ドキュメント内のインデックスをチェックし、すべてのドキュメントをスキャンするわけではないため、利点があります。結果を返すのにそれほど時間はかかりません。次のような別のデータ構造を使用している場合があります:
{ "_id" : NumberInt(1),
"grades" : {"Maths" : "A", "English" : "A", "Science" : "A", "History" : "B"
}
}
埋め込まれた成績フィールド全体ではなく、配列内の異なるエンティティとして成績を返したい場合があります。
上記のSQLクエリを記述した後、結果のMongoDBコードを変更する必要があります。これを行うには、以下のように右側のコピーアイコンをクリックして、集計コードをコピーします。
次に、[集計]タブに移動し、表示されたペインに貼り付けアイコンがあり、それをクリックしてコードを貼り付けます。
$ match行をクリックしてから、緑色の上矢印をクリックして、ステージを最初のステージとして一番上に移動します。ただし、最初に次のようにコレクションにインデックスを作成する必要があります。
db.students.createIndex(
{ _id: 1 },
{ name: studentId }
)
以下のコードサンプルを入手できます:
db.getCollection("students").aggregate(
[{ "$match" : {"_id" : 1.0}},
{ "$project" : {"_id" : NumberInt(0),"students" : "$$ROOT"}},
{ "$lookup" : {"localField" : "students._id","from" : "units","foreignField" : "_id","as" : "units"}},
{ "$unwind" : {"path" : "$units", "preserveNullAndEmptyArrays" : false}}
]
SomeninesがMongoDBDBAになる-MongoDBを本番環境に導入MongoDBDownloadを無料でデプロイ、監視、管理、スケーリングするために知っておくべきことを学びましょう このコードを使用すると、以下の結果が得られます:
{ "students" : {"_id" : NumberInt(1), "name" : "James Washington","age" : 15.0,"grade" : "A", "score" : 10.5},
"units" : {"_id" : NumberInt(1), "grades" : {"Maths" : "A", "English" : "A", "Science" : "A", "History" : "B"}}}
ただし、必要なのは、上記の例ではなく、返されたドキュメントに個別のドキュメントエンティティとして成績を設定することだけです。したがって、$ addfieldsステージを追加するため、以下のコードを追加します。
db.getCollection("students").aggregate(
[{ "$match" : {"_id" : 1.0}},
{ "$project" : {"_id" : NumberInt(0),"students" : "$$ROOT"}},
{ "$lookup" : {"localField" : "students._id","from" : "units","foreignField" : "_id","as" : "units"}},
{ "$unwind" : {"path" : "$units", "preserveNullAndEmptyArrays" : false}},
{ "$addFields" : {"units" : "$units.grades"} }]
結果のドキュメントは次のようになります。
{
"students" : {"_id" : NumberInt(1), "name" : "James Washington", "grade" : "A","score" : 10.5},
"units" : {"Maths" : "A", "English" : "A", "Science" : "A", "History" : "B"}
}
ユニットのコレクションから別のフィールドとして埋め込まれたドキュメントを削除したため、返されるデータは非常にきれいです。
次のチュートリアルでは、いくつかの結合があるクエリを調べます。