はじめに
3.2からaggregation Stageに $lookupが追加され、SQLのJOINっぽいことができるようになったので、試してみようと思います。
準備
部署IDを項目に持つ社員コレクションと、部署コレクションを用意します。
サンプルデータ
employee コレクション
/* 1 */
{
"_id" : ObjectId("588dea189433b6511a29fcee"),
"id" : "000001",
"name" : "Tanaka",
"sectionId" : "SEC-001",
"rank" : "SE-1"
}
/* 2 */
{
"_id" : ObjectId("588dea419433b6511a29fcef"),
"id" : "000002",
"name" : "Suzuki",
"sectionId" : "SEC-001",
"rank" : "SE-1"
}
/* 3 */
{
"_id" : ObjectId("588dea502e5af7712f36b140"),
"id" : "000003",
"name" : "Sato",
"sectionId" : "SEC-002",
"rank" : "SE-4"
}
/* 4 */
{
"_id" : ObjectId("588dea5e2e5af7712f36b14e"),
"id" : "000004",
"name" : "Tamura",
"sectionId" : "SEC-003",
"rank" : "GM-1"
}
section コレクション
/* 1 */
{
"_id" : ObjectId("588deb0c2e5af7712f36b1a1"),
"sectionId" : "SEC-001",
"sectionName" : "第一開発部"
}
/* 2 */
{
"_id" : ObjectId("588deb239433b6511a29fcf0"),
"sectionId" : "SEC-002",
"sectionName" : "第二開発部"
}
/* 3 */
{
"_id" : ObjectId("588deb5a9433b6511a29fcf1"),
"sectionId" : "SEC-003",
"sectionName" : "事務"
}
$lookupする
employeeコレクションを基準に、sectionコレクションを$lookupします。
db.employee.aggregate([
{$lookup:
{
from:"section",
localField:"sectionId",
foreignField:"sectionId",
as:"sectionInfos"
}
}
])
- from: 参照するコレクション
- localField: 結合に使用する、基準コレクション側の項目名
- foreignField: 結合に使用する、参照コレクション側の項目名
- as: 別名
この例では、localFieldとforeignFieldの項目名が同じものになっていますが、同じである必要はありません。
結果は以下の通り。
/* 1 */
{
"_id" : ObjectId("588dea189433b6511a29fcee"),
"id" : "000001",
"name" : "Tanaka",
"sectionId" : "SEC-001",
"rank" : "SE-1",
"sectionInfos" : [
{
"_id" : ObjectId("588deb0c2e5af7712f36b1a1"),
"sectionId" : "SEC-001",
"sectionName" : "第一開発部"
}
]
}
/* 2 */
{
"_id" : ObjectId("588dea419433b6511a29fcef"),
"id" : "000002",
"name" : "Suzuki",
"sectionId" : "SEC-001",
"rank" : "SE-1",
"sectionInfos" : [
{
"_id" : ObjectId("588deb0c2e5af7712f36b1a1"),
"sectionId" : "SEC-001",
"sectionName" : "第一開発部"
}
]
}
/* 3 */
{
"_id" : ObjectId("588dea502e5af7712f36b140"),
"id" : "000003",
"name" : "Sato",
"sectionId" : "SEC-002",
"rank" : "SE-4",
"sectionInfos" : [
{
"_id" : ObjectId("588deb239433b6511a29fcf0"),
"sectionId" : "SEC-002",
"sectionName" : "第二開発部"
}
]
}
/* 4 */
{
"_id" : ObjectId("588dea5e2e5af7712f36b14e"),
"id" : "000004",
"name" : "Tamura",
"sectionId" : "SEC-003",
"rank" : "GM-1",
"sectionInfos" : [
{
"_id" : ObjectId("588deb5a9433b6511a29fcf1"),
"sectionId" : "SEC-003",
"sectionName" : "事務"
}
]
}
$lookupした場所が、別名で配列になって返ってきました。
配列を取り出す
今回のサンプルでは、一人の社員は一つの部署にしか所属しないので、配列のままだと不格好です。というわけで、配列部を$unwindします。
あと、4レコードは多いので、$matchでTanakaさんだけに絞り込みます。
db.employee.aggregate([
{$match:{id:"000001"}},
{$lookup:
{
from:"section",
localField:"sectionId",
foreignField:"sectionId",
as:"sectionInfos"
}
},
{$unwind:"$sectionInfos"}
])
結果は次の通り。
/* 1 */
{
"_id" : ObjectId("588dea189433b6511a29fcee"),
"id" : "000001",
"name" : "Tanaka",
"sectionId" : "SEC-001",
"rank" : "SE-1",
"sectionInfos" : {
"_id" : ObjectId("588deb0c2e5af7712f36b1a1"),
"sectionId" : "SEC-001",
"sectionName" : "第一開発部"
}
}
必要な項目だけを取得する
最後に $projectを使用して必要な項目だけを取り出します。
db.employee.aggregate([
{$match:{id:"000001"}},
{$lookup:
{
from:"section",
localField:"sectionId",
foreignField:"sectionId",
as:"sectionInfos"
}
},
{$unwind:"$sectionInfos"},
{$project:
{
"id":"$id",
"name":"$name",
"sectionId":"$sectionId",
"sectionName":"$sectionInfos.sectionName",
"rank":"$rank"
}
}
])
WHEREを書いて、JOINを書いて、SELECTを書くみたいなイメージになりました。
※ この順番で記述しなければならないわけではないので、目的に応じて最適な順序で記述するようにしてください。
結果は次の通り
/* 1 */
{
"_id" : ObjectId("588dea189433b6511a29fcee"),
"id" : "000001",
"name" : "Tanaka",
"sectionId" : "SEC-001",
"sectionName" : "第一開発部",
"rank" : "SE-1"
}
例示はしませんが、$lookupでsectionからもってきた項目を使って、さらに別のコレクションを$lookupするなんてことも可能です。
おわりに
これでデータを取得する面からは、MongoDBでも正規化したデータ構造を利用することができますね。
ただ、MongoDBはトランザクションをサポートしていませんので、正規化したコレクションを完全にCRUD操作するにはアプリケーション側で、2相コミットなどの機構を構築するなどの工夫が必要なようです。
誤りなどありましたら、コメントにご指摘をいただけますと幸いです。