MongoDBコレクションのオブジェクト配列内の照会された要素のみを取得します


377

私のコレクションに次のドキュメントがあるとします。

{  
   "_id":ObjectId("562e7c594c12942f08fe4192"),
   "shapes":[  
      {  
         "shape":"square",
         "color":"blue"
      },
      {  
         "shape":"circle",
         "color":"red"
      }
   ]
},
{  
   "_id":ObjectId("562e7c594c12942f08fe4193"),
   "shapes":[  
      {  
         "shape":"square",
         "color":"black"
      },
      {  
         "shape":"circle",
         "color":"green"
      }
   ]
}

クエリを実行します。

db.test.find({"shapes.color": "red"}, {"shapes.color": 1})

または

db.test.find({shapes: {"$elemMatch": {color: "red"}}}, {"shapes.color": 1})

一致したドキュメント(ドキュメント1)を返しますが、常にすべての配列項目が含まれshapesます。

{ "shapes": 
  [
    {"shape": "square", "color": "blue"},
    {"shape": "circle", "color": "red"}
  ] 
}

ただし、以下を含む配列でのみドキュメント(ドキュメント1)を取得したいと思いますcolor=red

{ "shapes": 
  [
    {"shape": "circle", "color": "red"}
  ] 
}

これどうやってするの?

回答:


416

MongoDB 2.2の新しい$elemMatch射影演算子は、最初に一致したshapes要素のみを含むように返されたドキュメントを変更する別の方法を提供します。

db.test.find(
    {"shapes.color": "red"}, 
    {_id: 0, shapes: {$elemMatch: {color: "red"}}});

戻り値:

{"shapes" : [{"shape": "circle", "color": "red"}]}

2.2 $ projection operatorでは、を使用してこれを行うこともできます。$射影オブジェクトのフィールド名は、クエリからのフィールドの最初に一致する配列要素のインデックスを表します。以下は、上記と同じ結果を返します。

db.test.find({"shapes.color": "red"}, {_id: 0, 'shapes.$': 1});

MongoDB 3.2アップデート

3.2リリース以降では、新しい$filter集計演算子を使用して、射影中に配列をフィルタリングできます。これには、最初のものだけでなく、すべての一致を含めるという利点があります。

db.test.aggregate([
    // Get just the docs that contain a shapes element where color is 'red'
    {$match: {'shapes.color': 'red'}},
    {$project: {
        shapes: {$filter: {
            input: '$shapes',
            as: 'shape',
            cond: {$eq: ['$$shape.color', 'red']}
        }},
        _id: 0
    }}
])

結果:

[ 
    {
        "shapes" : [ 
            {
                "shape" : "circle",
                "color" : "red"
            }
        ]
    }
]

16
最初のものだけでなく、それに一致するすべての要素を返すようにしたい場合の解決策はありますか?
Steve Ng

私が使用しています怖いモンゴ3.0.Xを :-(
charliebrownie

@charliebrownie次に、を使用する他の回答の1つを使用しますaggregate
JohnnyHK、2016年

このクエリは配列「形状」のみを返し、他のフィールドは返しません。誰もが他のフィールドを返す方法も知っていますか?
Mark Thien

1
これも機能します:db.test.find({}, {shapes: {$elemMatch: {color: "red"}}});
Paul

97

MongoDB 2.2以降の新しい集約フレームワークは、Map / Reduceの代替手段を提供します。$unwindオペレータは、あなた分離するために使用することができるshapes一致させることができる書類の流れの中に配列を:

db.test.aggregate(
  // Start with a $match pipeline which can take advantage of an index and limit documents processed
  { $match : {
     "shapes.color": "red"
  }},
  { $unwind : "$shapes" },
  { $match : {
     "shapes.color": "red"
  }}
)

結果:

{
    "result" : [
        {
            "_id" : ObjectId("504425059b7c9fa7ec92beec"),
            "shapes" : {
                "shape" : "circle",
                "color" : "red"
            }
        }
    ],
    "ok" : 1
}

7
@JohnnyHK:この場合、$elemMatch別のオプションです。$ elemMatchは、ドキュメントごとに最初の一致のみを返すため、機能しないというGoogleグループの質問を通じて、実際にここに行きました。
ステニー

1
ありがとう、私はその制限を認識していなかったので、知っておくと良いでしょう。返信しているコメントを削除して申し訳ありません。代わりに別の回答を投稿することにしました。ユーザーを混乱させたくありませんでした。
JohnnyHK

3
@JohnnyHK:心配ありません。質問には3つの役立つ回答があります;-)
Stennie

他のサーチャーのために、これに加えて私も追加し{ $project : { shapes : 1 } }てみました-機能しているようで、囲んでいるドキュメントが大きく、shapesキー値を表示したいだけの場合に役立ちます。
user1063287 14

2
@calmbird最初の$ matchステージを含めるようにサンプルを更新しました。より効率的な機能の提案に興味がある場合は、SERVER-6612を監視/ 賛成します。MongoDBの課題トラッカーの$ elemMatchプロジェクション指定子のように、プロジェクションで複数の配列値のプロジェクションをサポートします
Stennie、2015年

30

別の興味深い方法は、$ redactを使用することです。これは、MongoDB 2.6の新しい集約機能の1つです。2.6を使用している場合、大きな配列がある場合にパフォーマンスの問題を引き起こす可能性がある$ unwindは必要ありません。

db.test.aggregate([
    { $match: { 
         shapes: { $elemMatch: {color: "red"} } 
    }},
    { $redact : {
         $cond: {
             if: { $or : [{ $eq: ["$color","red"] }, { $not : "$color" }]},
             then: "$$DESCEND",
             else: "$$PRUNE"
         }
    }}]);

$redact 「ドキュメント自体に格納されている情報に基づいてドキュメントの内容を制限します」。したがって、ドキュメントの内部でのみ実行されます。基本的にドキュメントを上から下にスキャンし、にあるif条件と一致するかどうかをチェックします。一致する$cond場合は、content($$DESCEND)またはremove($$PRUNE)を保持します。

上記の例では、最初に配列$match全体を返し、shapes$ redactはそれを期待される結果まで削除します。

{$not:"$color"}必要なことに注意してください。これは、最上位のドキュメントもスキャンするためです。最上位のフィールドが$redact見つからない場合は、ドキュメント全体が取り除かれる可能性colorがあるためfalse、これが返されます。


1
完璧な答え。あなたが言及したように、$ unwindは多くのRAMを消費します。ですから、これは比較するとより良いでしょう。
manojpt 2015

私は疑問を持っています。この例では、「形状」は配列です。「$ redact」は「shapes」配列のすべてのオブジェクトをスキャンしますか?これはパフォーマンスに関してどのように優れていますか?
manojpt 2015

すべてではありませんが、最初の試合の結果です。これが、$match最初の集計ステージとして配置した理由です
anvarik

okkk ..「color」フィールドにインデックスが作成された場合でも、「shapes」配列のすべてのオブジェクトをスキャンしますか??? 配列内の複数のオブジェクトを照合する効率的な方法はどれですか?
manojpt 2015

2
鮮やかさ!ここで$ eqがどのように機能するのかわかりません。私はそれを最初に中断しました、そしてこれは私のためにうまくいきませんでした。どういうわけか、一致するものを見つけるために形状の配列を調べますが、クエリはどの配列を調べるかを指定することはありません。たとえば、ドキュメントに形状やサイズなどがある場合は、$ eqは両方の配列で一致を探しますか?$ redactは、ドキュメント内で「if」条件に一致するものを探しているだけですか?
Onosa

30

注意:この回答は、MongoDB 2.2以降の新機能が導入される前の、当時関連していたソリューションを提供します。より新しいバージョンのMongoDBを使用している場合は、他の回答を参照してください。

フィールドセレクターパラメーターは、完全なプロパティに制限されています。配列全体ではなく、配列の一部を選択するために使用することはできません。$位置演算子を使ってみましたが、うまくいきませんでした。

最も簡単な方法は、クライアントの形状フィルターするだけです

MongoDBから直接正しい出力が本当に必要な場合は、map-reduceを使用して形状をフィルタリングできます。

function map() {
  filteredShapes = [];

  this.shapes.forEach(function (s) {
    if (s.color === "red") {
      filteredShapes.push(s);
    }
  });

  emit(this._id, { shapes: filteredShapes });
}

function reduce(key, values) {
  return values[0];
}

res = db.test.mapReduce(map, reduce, { query: { "shapes.color": "red" } })

db[res.result].find()

24

配列$sliceの重要なオブジェクトを返すと、配列要素の一致を照会できるので便利です。

db.test.find({"shapes.color" : "blue"}, {"shapes.$" : 1})

$slice要素のインデックスがわかっている場合に役立ちますが、条件に一致する配列要素が必要になる場合があります。$演算子を使用して、一致する要素を返すことができます。


19
 db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1})

出力

{

   "shapes" : [ 
       {
           "shape" : "circle",
           "color" : "red"
       }
   ]
}

12

mongodbでのfindの構文は次のとおりです。

    db.<collection name>.find(query, projection);

そして、あなたが書いた2番目のクエリ、つまり

    db.test.find(
    {shapes: {"$elemMatch": {color: "red"}}}, 
    {"shapes.color":1})

この例では$elemMatch、クエリパーツで演算子を使用していますが、この演算子をプロジェクションパーツで使用すると、目的の結果が得られます。次のようにクエリを書き留めることができます

     db.users.find(
     {"shapes.color":"red"},
     {_id:0, shapes: {$elemMatch : {color: "red"}}})

これにより、望ましい結果が得られます。


1
これでうまくいきます。ただし、"shapes.color":"red"クエリパラメータ(findメソッドの最初のパラメータ)では必要ないようです。置き換えることが{}でき、同じ結果が得られます。
エリックオルソン2014年

2
@ErikOlsonあなたの提案は、赤色のすべてのドキュメントを検索し、それらにのみ投影を適用する必要がある上記の場合に当てはまります。しかし、誰かが青を持っているすべてのドキュメントを見つける必要があるけれども、赤を持っているその形状配列のそれらの要素だけを返すべきであるとしましょう。この場合、上記のクエリが...また他の誰かによって参照することができます
ヴィッキー

これが一番簡単なようですが、うまく動かせません。最初に一致したサブドキュメントのみを返します。
ニューマン

8

JohnnyHKに感謝します

ここでは、さらに複雑な使用法を追加したいだけです。

// Document 
{ 
"_id" : 1
"shapes" : [
  {"shape" : "square",  "color" : "red"},
  {"shape" : "circle",  "color" : "green"}
  ] 
} 

{ 
"_id" : 2
"shapes" : [
  {"shape" : "square",  "color" : "red"},
  {"shape" : "circle",  "color" : "green"}
  ] 
} 


// The Query   
db.contents.find({
    "_id" : ObjectId(1),
    "shapes.color":"red"
},{
    "_id": 0,
    "shapes" :{
       "$elemMatch":{
           "color" : "red"
       } 
    }
}) 


//And the Result

{"shapes":[
    {
       "shape" : "square",
       "color" : "red"
    }
]}

7

クエリを実行するだけです

db.test.find(
{"shapes.color": "red"}, 
{shapes: {$elemMatch: {color: "red"}}});

このクエリの出力は

{
    "_id" : ObjectId("562e7c594c12942f08fe4192"),
    "shapes" : [ 
        {"shape" : "circle", "color" : "red"}
    ]
}

予想通り、color: 'red'に一致する配列からの正確なフィールドを提供します。


3

$ projectと共に、他の賢明なマッチング要素がドキュメント内の他の要素と結合されます。

db.test.aggregate(
  { "$unwind" : "$shapes" },
  { "$match" : {
     "shapes.color": "red"
  }},
{"$project":{
"_id":1,
"item":1
}}
)

入力と出力のセットでこれが達成されることをplsは説明できますか?
Alexander Mills

2

同様に、あなたは複数のために見つけることができます

db.getCollection('localData').aggregate([
    // Get just the docs that contain a shapes element where color is 'red'
  {$match: {'shapes.color': {$in : ['red','yellow'] } }},
  {$project: {
     shapes: {$filter: {
        input: '$shapes',
        as: 'shape',
        cond: {$in: ['$$shape.color', ['red', 'yellow']]}
     }}
  }}
])

この回答は、実際に推奨される4.xの方法です。 $matchスペースを削減し、$filter必要なものを保持し、入力フィールドを上書きします($filteron fieldの出力を使用shapesしてに$project戻りshapesます。スタイルの注記:フィールド名をas引数それは、後で混乱を招く可能性があるため$$shape$shape、私は好む。zzとしてasそれは本当に際立っているので、フィールド。
バズMoschetti

1
db.test.find( {"shapes.color": "red"}, {_id: 0})

1
Stack Overflowへようこそ!コードスニペットをご利用いただきありがとうございます。このコードスニペットは、限られた範囲内ですぐに役立つ可能性があります。適切な説明は、なぜこれが問題の優れた解決策であるかを説明することにより、長期的な価値を大幅に改善し、他の同様の質問を持つ将来の読者にとってさらに役立つでしょう。答えを編集して、仮定を含めて説明を追加してください。
sepehr 2018年

1

集計関数を使用して$project、ドキュメント内の特定のオブジェクトフィールドを取得する

db.getCollection('geolocations').aggregate([ { $project : { geolocation : 1} } ])

結果:

{
    "_id" : ObjectId("5e3ee15968879c0d5942464b"),
    "geolocation" : [ 
        {
            "_id" : ObjectId("5e3ee3ee68879c0d5942465e"),
            "latitude" : 12.9718313,
            "longitude" : 77.593551,
            "country" : "India",
            "city" : "Chennai",
            "zipcode" : "560001",
            "streetName" : "Sidney Road",
            "countryCode" : "in",
            "ip" : "116.75.115.248",
            "date" : ISODate("2020-02-08T16:38:06.584Z")
        }
    ]
}

0

質問は9.6年前に行われましたが、これは多くの人々に非常に役立ちました。私もその1人です。ご質問、ヒント、回答をありがとうございました。ここで答えの1つからピックアップします。次の方法は、親ドキュメントの他のフィールドを投影するためにも使用できることがわかりました。これは、誰かに役立つかもしれません。

次のドキュメントでは、従業員(emp#7839)の休暇履歴が2020年に設定されているかどうかを確認する必要がありました。休暇履歴は、親従業員ドキュメント内の埋め込みドキュメントとして実装されます。

db.employees.find( {"leave_history.calendar_year": 2020}, 
    {leave_history: {$elemMatch: {calendar_year: 2020}},empno:true,ename:true}).pretty()


{
        "_id" : ObjectId("5e907ad23997181dde06e8fc"),
        "empno" : 7839,
        "ename" : "KING",
        "mgrno" : 0,
        "hiredate" : "1990-05-09",
        "sal" : 100000,
        "deptno" : {
                "_id" : ObjectId("5e9065f53997181dde06e8f8")
        },
        "username" : "none",
        "password" : "none",
        "is_admin" : "N",
        "is_approver" : "Y",
        "is_manager" : "Y",
        "user_role" : "AP",
        "admin_approval_received" : "Y",
        "active" : "Y",
        "created_date" : "2020-04-10",
        "updated_date" : "2020-04-10",
        "application_usage_log" : [
                {
                        "logged_in_as" : "AP",
                        "log_in_date" : "2020-04-10"
                },
                {
                        "logged_in_as" : "EM",
                        "log_in_date" : ISODate("2020-04-16T07:28:11.959Z")
                }
        ],
        "leave_history" : [
                {
                        "calendar_year" : 2020,
                        "pl_used" : 0,
                        "cl_used" : 0,
                        "sl_used" : 0
                },
                {
                        "calendar_year" : 2021,
                        "pl_used" : 0,
                        "cl_used" : 0,
                        "sl_used" : 0
                }
        ]
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.