web-dev-qa-db-fra.com

Récupérer uniquement l'élément recherché dans un tableau d'objets de la collection MongoDB

Supposons que vous ayez les documents suivants dans ma collection:

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

Faire une requête:

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

Ou

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

Retourne le document correspondant (Document 1) , mais toujours avec TOUS les éléments de tableau dans shapes:

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

Cependant, je voudrais obtenir le document (Document 1) uniquement avec le tableau contenant color=red:

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

Comment puis-je faire ceci?

313
Sebtm

Le nouvel opérateur $elemMatch de projection de MongoDB 2.2 offre un autre moyen de modifier le document renvoyé afin qu'il ne contienne que l'élément premier correspondant shapes:

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

Résultats:

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

En 2.2, vous pouvez également le faire en utilisant $ projection operator , où $ dans un nom de champ d'objet de projection représente l'index du premier élément de tableau correspondant du champ à partir de la requête. Ce qui suit renvoie les mêmes résultats que ci-dessus:

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

MongoDB 3.2 Mise à jour

À partir de la version 3.2, vous pouvez utiliser le nouvel opérateur $filter pour filtrer un tableau pendant la projection, ce qui présente l’avantage d’inclure les correspondances tout au lieu du premier.

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
    }}
])

Résultats:

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

Le nouveau Aggregation Framework dans MongoDB 2.2+ constitue une alternative à Map/Reduce. L'opérateur $unwind peut être utilisé pour séparer votre tableau shapes en un flux de documents pouvant correspondre:

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"
  }}
)

Résulte en:

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

Attention: Cette réponse fournit une solution pertinente à cette époque avant que les nouvelles fonctionnalités de MongoDB 2.2 et versions ultérieures soient introduites. Voir les autres réponses si vous utilisez une version plus récente de MongoDB.

Le paramètre de sélection de champ est limité à des propriétés complètes. Il ne peut pas être utilisé pour sélectionner une partie d'un tableau, mais uniquement le tableau entier. J'ai essayé d'utiliser l'opérateur $ positional , mais cela n'a pas fonctionné.

Le plus simple consiste à filtrer les formes dans le client.

Si vous avez vraiment besoin la sortie correcte directement à partir de MongoDB, vous pouvez utiliser une carte-réduction pour filtrer les formes.

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()
29
Niels van der Rest

Une autre méthode intéressante consiste à utiliser $ redact , qui est l’une des nouvelles fonctionnalités d’agrégation de MongoDB 2.6. Si vous utilisez la version 2.6, vous n'avez pas besoin d'un $ $ déroulement, ce qui pourrait vous causer des problèmes de performances si vous avez de grands tableaux. 

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

$redact"restreint le contenu des documents en fonction des informations stockées dans les documents eux-mêmes". Ainsi, il n’exécutera que à l’intérieur du document. En gros, il analyse votre document de haut en bas et vérifie s'il correspond à votre condition if qui est dans $cond. S'il y a correspondance, il conserve le contenu ($$DESCEND) ou le supprime ($$Prune).

Dans l'exemple ci-dessus, le premier $match renvoie le tableau entier shapes et $ redact le réduit au résultat attendu. 

Notez que {$not:"$color"} est nécessaire, car il numérisera également le document supérieur et si $redact ne trouve pas de champ color au niveau supérieur, cela renverra false qui pourrait supprimer tout le document que nous ne souhaitons pas. 

27
anvarik

Mieux, vous pouvez interroger un élément de tableau correspondant à l'aide de $slice. Est-il utile de renvoyer l'objet significatif dans un tableau?.

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

$slice est utile lorsque vous connaissez l'index de l'élément, mais que vous souhaitez parfois quel que soit l'élément de tableau correspondant à vos critères. Vous pouvez retourner l'élément correspondant .__ avec l'opérateur $.

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

LES SORTIES

{

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

La syntaxe de find dans mongodb est 

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

et la deuxième requête que vous avez écrite, c'est

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

vous avez utilisé pour cela l'opérateur $elemMatch dans la partie requête, alors que si vous utilisez cet opérateur dans la partie projection, vous obtenez le résultat souhaité. Vous pouvez écrire votre requête en tant que 

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

Cela vous donnera le résultat souhaité.

11
Vicky

Merci à JohnnyHK.

Ici, je veux juste ajouter un usage plus complexe.

// 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
Eddy

Vous avez juste besoin d'exécuter la requête

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

la sortie de cette requête est

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

comme vous vous y attendiez, le champ exact du tableau qui correspond à la couleur est «rouge».

5
Vaibhav Patil

avec $ project, il sera plus approprié que d’autres éléments de correspondance judicieux soient associés à d’autres éléments du document. 

db.test.aggregate(
  { "$unwind" : "$shapes" },
  { "$match" : {
     "shapes.color": "red"
  }},
{"$project":{
"_id":1,
"item":1
}}
)
2
shakthydoss
db.test.find( {"shapes.color": "red"}, {_id: 0})
0
Poonam Agrawal