LoginSignup
57

More than 5 years have passed since last update.

MongoDBでネストされたone to manyのドキュメントを検索条件にする

Posted at

やり方がわからなかったのでわかるところから少しずつ試してみました。その結果をログっときます。

問題を単純なところから噛み砕くためにネストされた one to one のドキュメントを検索条件にしてみます。
下記のようなドキュメントが格納されている場合。

db.items.insert([{
    name: "pencil",
    category: "stationery",
    price: 105,
    stock: 12,
    owner: {
        name: "john",
        gender: "male"
    }
},
{
    name: "eraser",
    category: "stationery",
    price: 140,
    stock: 5,
    owner: {
        name: "alice",
        gender: "female"
    }
},
{
    name: "ballpoint",
    category: "stationery",
    price: 110,
    stock: 7,
    owner: {
        name: "mike",
        gender: "male"
    }    
}
]);

普通にfindしたらこうなりますね。

> db.items.find();

{ 
    "_id" : ObjectId("53d655093a8590ad7c80bae9"), "name" : "pencil", "category" : "stationery", "price" : 105, "stock" : 12, 
    "owner" : { "name" : "john", "gender" : "male" } 
}
{
     "_id" : ObjectId("53d655093a8590ad7c80baea"), "name" : "eraser", "category" : "stationery", "price" : 140, "stock" : 5,
     "owner" : { "name" : "alice", "gender" : "female" }
}
{
     "_id" : ObjectId("53d655093a8590ad7c80baeb"), "name" : "ballpoint", "category" : "stationery", "price" : 110, "stock" : 7,
     "owner" : { "name" : "mike", "gender" : "male" }
}

ではone to oneのドキュメントであるowner.gender(性別)を検索条件にしてみます。
aliceは除外されてjohnとmikeのドキュメントが取得されましたね。この辺は単純です。

> db.items.find({"owner.gender": "male"});

{
     "_id" : ObjectId("53d655093a8590ad7c80bae9"), "name" : "pencil", "category" : "stationery", "price" : 105, "stock" : 12, 
     "owner" : { "name" : "john", "gender" : "male" }
}
{
     "_id" : ObjectId("53d655093a8590ad7c80baeb"), "name" : "ballpoint", "category" : "stationery", "price" : 110, "stock" : 7,
     "owner" : { "name" : "mike", "gender" : "male" }
}

次はドキュメントの構造が少し変わります。
itemにはownerが複数人いる構造にしてみます(例が悪い?すまんな)。

# 一回全部削除
db.items.remove();

db.items.insert([{
    name: "pencil",
    category: "stationery",
    price: 105,
    stock: 12,
    owners: [
        { code: 1001, name: "john"},
        { code: 1002, name: "alice"},
        { code: 1003, name: "mike"}
    ]
},
{
    name: "eraser",
    category: "stationery",
    price: 140,
    stock: 5,
    owners: [
        { code: 1001, name: "john"},
        { code: 1004, name: "adam"},
        { code: 1005, name: "bob"}
    ]
},
{
    name: "ballpoint",
    category: "stationery",
    price: 110,
    stock: 7,
    owners: [
        { code: 1006, name: "dan"},
        { code: 1004, name: "adam"},
        { code: 1007, name: "dean"}
    ]
}
]);

ownerのcodeが1004を含むドキュメントを取得してみます。

> db.items.find({ owners: { $elemMatch: { code: 1004 } } })

{
    "_id" : ObjectId("53d656cf3a8590ad7c80baf0"), "name" : "eraser", "category" : "stationery", "price" : 140, "stock" : 5,
    "owners" : [
        { "code" : 1001, "name" : "john" },
        { "code" : 1004, "name" : "adam" },
        { "code" : 1005, "name" : "bob" }
    ]
}
{
    "_id" : ObjectId("53d656cf3a8590ad7c80baf1"), "name" : "ballpoint", "category" : "stationery", "price" : 110, "stock" : 7,
    "owners" : [
        { "code" : 1006, "name" : "dan" },
        { "code" : 1004, "name" : "adam" },
        { "code" : 1006, "name" : "dean" }
    ]
}

さて次はownerのcodeが1001と1004の両方を含むドキュメントを抽出したいとします。
あれれ?ちょっと期待したのと違いましたね。

$inを使うと、いずれかを含んでいるドキュメントが抽出されてしまうようです。

> db.items.find({ owners: { $elemMatch: { code: { $in: [1001,1004] } } } })

{
    "_id" : ObjectId("53d656cf3a8590ad7c80baef"), "name" : "pencil", "category" : "stationery", "price" : 105, "stock" : 12,
    "owners" : [
        { "code" : 1001, "name" : "john" },
        { "code" : 1002, "name" : "alice" },
        { "code" : 1003, "name" : "mike" }
    ]
}
{
    "_id" : ObjectId("53d656cf3a8590ad7c80baf0"), "name" : "eraser", "category" : "stationery", "price" : 140, "stock" : 5,
    "owners" : [
        { "code" : 1001, "name" : "john" },
        { "code" : 1004, "name" : "adam" },
        { "code" : 1005, "name" : "bob" 
    ]
}
{
    "_id" : ObjectId("53d656cf3a8590ad7c80baf1"), "name" : "ballpoint", "category" : "stationery", "price" : 110, "stock" : 7,
    "owners" : [
        { "code" : 1006, "name" : "dan" },
        { "code" : 1004, "name" : "adam" },
        { "code" : 1006, "name" : "dean" }
    ]
}

このように$allを使用すれば1001, 1004を含むドキュメントが抽出出来ました。

> db.items.find({ owners: { $all: [{ $elemMatch: { code:1001 } }, { $elemMatch: { code: 1004 } }] } } )

{
    "_id" : ObjectId("53d656cf3a8590ad7c80baf0"), "name" : "eraser", "category" : "stationery", "price" : 140, "stock" : 5,
    "owners" : [
        { "code" : 1001, "name" : "john" },
        { "code" : 1004, "name" : "adam" },
        { "code" : 1005, "name" : "bob" }
    ]
}

ちなみにこのように書いても同じ結果っぽい?こっちの方がシンプルでいい。

> db.items.find({ "owners.code": { $all: [1001, 1004] } })

{
    "_id" : ObjectId("53d656cf3a8590ad7c80baf0"), "name" : "eraser", "category" : "stationery", "price" : 140, "stock" : 5,
    "owners" : [
        { "code" : 1001, "name" : "john" },
        { "code" : 1004, "name" : "adam" }, 
        { "code" : 1005, "name" : "bob" }
    ]
}

さて、問題はowners.codeの配列の要素を完全に一致させて抽出したいときのやりかたに困りました。そんなときひらめいたのが$sizeというオペレータ。

つまり条件に指定された配列要素が全て存在しており、かつ要素数が一致していれば条件を満たすわけですね。

> db.items.find({ "owners.code": { $all: [1001, 1002, 1003] }, "owners": { $size: 3 } })

{
    "_id" : ObjectId("53d656cf3a8590ad7c80baef"), "name" : "pencil", "category" : "stationery", "price" : 105, "stock" : 12,
    "owners" : [
        { "code" : 1001, "name" : "john" },
        { "code" : 1002, "name" : "alice" },
        { "code" : 1003, "name" : "mike" } 
    ]
}

なんかスマートじゃない気はする。他にやり方はあるのだろうか?
そもそもMongoのスキーマの設計がいけてないのかな?

他に手段があれば教えて欲しいっす。

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
57