33
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

MongoDBでJOINっぽいことをしてみる

Last updated at Posted at 2017-01-29

はじめに

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相コミットなどの機構を構築するなどの工夫が必要なようです。

誤りなどありましたら、コメントにご指摘をいただけますと幸いです。

33
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?