web-dev-qa-db-ja.com

MongoDBコレクションで特定の基準に一致するドキュメントと単一のサブドキュメントを見つける方法

商品のコレクションがあります。各製品には、アイテムの配列が含まれています。

> db.products.find().pretty()
{
    "_id" : ObjectId("54023e8bcef998273f36041d"),
    "shop" : "shop1",
    "name" : "product1",
    "items" : [
            {
                    "date" : "01.02.2100",
                    "purchasePrice" : 1,
                    "sellingPrice" : 10,
                    "count" : 15
            },
            {
                    "date" : "31.08.2014",
                    "purchasePrice" : 10,
                    "sellingPrice" : 1,
                    "count" : 5
            }
    ]
}

ですから、MongoDBにクエリを実行して、パラメーターとしてクエリに渡した日付と同じ日付の単一のアイテムのみを含むすべての製品を取得する方法を教えてください。

「31.08.2014」の結果は次のようになります。

    {
    "_id" : ObjectId("54023e8bcef998273f36041d"),
    "shop" : "shop1",
    "name" : "product1",
    "items" : [
            {
                    "date" : "31.08.2014",
                    "purchasePrice" : 10,
                    "sellingPrice" : 1,
                    "count" : 5
            }
    ]
}
14
evgeniy44

あなたが探しているのは 定位置_$_ 演算子と「射影」です。単一のフィールドの場合は、「ドット表記」を使用して必要な配列要素と一致させる必要があります。複数のフィールドの場合は _$elemMatch_

_db.products.find(
    { "items.date": "31.08.2014" },
    { "shop": 1, "name":1, "items.$": 1 }
)
_

または、一致する複数のフィールドの_$elemMatch_:

_db.products.find(
    { "items":  { 
        "$elemMatch": { "date": "31.08.2014",  "purchasePrice": 1 }
    }},
    { "shop": 1, "name":1, "items.$": 1 }
)
_

ただし、これらは単一の配列要素に対してのみ機能し、1つだけが返されます。条件から複数の配列要素を返す場合は、集計フレームワークでのより高度な処理が必要です。

_db.products.aggregate([
    { "$match": { "items.date": "31.08.2014" } },
    { "$unwind": "$items" },
    { "$match": { "items.date": "31.08.2014" } },
    { "$group": {
        "_id": "$_id",
        "shop": { "$first": "$shop" },
        "name": { "$first": "$name" },
        "items": { "$Push": "$items" }
    }}
])
_

または、MongoDB 2.6以降、項目の配列に一意のエントリが含まれているため、おそらくより短い/より速い形式で:

_db.products.aggregate([
    { "$match": { "items.date": "31.08.2014" } },
    { "$project": {
        "shop": 1,
        "name": 1,
        "items": {
            "$setDifference": [
                { "$map": {
                    "input": "$items",
                    "as": "el",
                    "in": {
                        "$cond": [
                            { "$eq": [ "$$el.date", "31.08.2014" ] },
                            "$$el",
                            false 
                        ]
                    }
                }},
                [false]
            ]
        }
    }}
])
_

あるいは、おそらく _$redact_ を使用しますが、少し工夫されています:

_db.products.aggregate([
    { "$match": { "items.date": "31.08.2014" } },
    { "$redact": {
        "$cond": [
             { "$eq": [ { "$ifNull": [ "$date", "31.08.2014" ] }, "31.08.2014" ] },
             "$$DESCEND",
             "$$Prune"
         ]
    }}
])
_

より現代的には、_$filter_を使用します。

_db.products.aggregate([
  { "$match": { "items.date": "31.08.2014" } },
  { "$addFields": {
    "items": {
      "input": "$items",
      "cond": { "$eq": [ "$$this.date", "31.08.2014" ] }
    }
  }}
])
_

そして、複数の条件がある場合、_$elemMatch_内の_$and_および_$filter_:

_db.products.aggregate([
  { "$match": { 
    "$elemMatch": { "date": "31.08.2014",  "purchasePrice": 1 }
  }},
  { "$addFields": {
    "items": {
      "input": "$items",
      "cond": { 
        "$and": [
          { "$eq": [ "$$this.date", "31.08.2014" ] },
          { "$eq": [ "$$this.purchasePrice", 1 ] }
        ]
      }
    }
  }}
])
_

したがって、単一の要素が常に一致することを期待するか、複数の要素を期待するか、そしてどちらのアプローチがより良いかに依存します。しかし、可能な場合、.find()メソッドは他の操作のオーバーヘッドがないため、一般的に高速になります。これらの操作では、最後に、フォームにまったく遅れが生じません。

補足として、あなたの「日付」は文字列として表されますが、これはこれからはあまり良い考えではありません。これらを適切な Date オブジェクトタイプに変更することを検討してください。これは、将来的に非常に役立ちます。

37
Neil Lunn

Mongoはサブクエリのドット表記をサポートしています。

参照: http://docs.mongodb.org/manual/reference/glossary/#term-dot-notation

ドライバーによっては、次のようなものが必要です。

db.products.find({"items.date":"31.08.2014"});

通常、ドライバーがこれを必要としない場合でも、属性はドット表記の引用符で囲まれていることに注意してください。

0