474 votes

Récupérer uniquement l'élément interrogé dans un tableau d'objets dans une 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"
      }
   ]
}

Faites des requêtes :

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 du tableau dans shapes :

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

Cependant, j'aimerais obtenir le document (Document 1) uniquement avec le tableau qui contient color=red :

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

Comment puis-je le faire ?

523voto

JohnnyHK Points 61191

Les nouveautés de MongoDB 2.2 $elemMatch fournit un autre moyen de modifier le document retourné pour qu'il ne contienne que les éléments suivants premièrement jumelé shapes élément :

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

Les retours :

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

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

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

Mise à jour de MongoDB 3.2

À partir de la version 3.2, vous pouvez utiliser la nouvelle fonction $filter opérateur d'agrégation pour filtrer un tableau lors de la projection, ce qui a pour avantage d'inclure todo au lieu de la première.

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

32 votes

Une solution si je veux qu'il renvoie tous les éléments qui correspondent au lieu du premier ?

0 votes

J'ai peur d'utiliser Mongo 3.0.X :-(

0 votes

@charliebrownie Ensuite, utilisez une des autres réponses qui utilisent aggregate .

116voto

Stennie Points 19196

Le nouveau Cadre d'agrégation dans MongoDB 2.2+ offre une alternative à Map/Reduce. Le site $unwind peut être utilisé pour séparer votre shapes en un flux de documents qui peuvent être mis en correspondance :

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ésultats dans :

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

7 votes

@JohnnyHK : Dans ce cas, $elemMatch est une autre option. En fait, je suis arrivé ici par le biais d'un Question sur le groupe Google où $elemMatch ne fonctionnerait pas car il ne renvoie que la première correspondance par document.

1 votes

Merci, je n'étais pas au courant de cette limitation, c'est bon à savoir. Désolé d'avoir supprimé mon commentaire auquel vous répondez, j'ai décidé de poster une autre réponse à la place et je ne voulais pas embrouiller les gens.

3 votes

@JohnnyHK : Pas d'inquiétude, il y a maintenant trois réponses utiles à la question ;-)

34voto

Niels van der Rest Points 11802

Attention : Cette réponse fournit une solution qui était pertinente à cette époque avant l'introduction des nouvelles fonctionnalités de MongoDB 2.2 et plus. Consultez les autres réponses si vous utilisez une version plus récente de MongoDB.

Le paramètre du sélecteur de champ est limité aux propriétés complètes. Il ne peut pas être utilisé pour sélectionner une partie d'un tableau, mais uniquement l'ensemble du tableau. J'ai essayé d'utiliser le paramètre $ opérateur positionnel mais ça n'a pas marché.

La méthode la plus simple consiste à filtrer les formes. dans le client .

Si vous voulez vraiment besoin de la sortie correcte directement de MongoDB, vous pouvez utiliser un map-reduce 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()

33voto

anvarik Points 1614

Un autre moyen intéressant est d'utiliser $redact qui est l'une des nouvelles fonctions d'agrégation de l'UE. MongoDB 2.6 . Si vous utilisez la version 2.6, vous n'avez pas besoin d'un $unwind qui pourrait vous causer des problèmes de performance 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 sur la base des informations stockées dans les documents eux-mêmes". . Ainsi, il ne fonctionnera que à l'intérieur du document . Il numérise votre document de haut en bas, et vérifie s'il correspond à votre logo. if qui est en $cond s'il y a une correspondance, il conservera le contenu ( $$DESCEND ) ou remove( $$PRUNE ).

Dans l'exemple ci-dessus, on commence par $match retourne l'ensemble du 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 color au niveau supérieur, cela donnera false qui pourrait dépouiller le document entier, ce que nous ne voulons pas.

1 votes

Réponse parfaite. Comme vous l'avez mentionné, $unwind consommera beaucoup de RAM. C'est donc une meilleure solution en comparaison.

0 votes

J'ai un doute. Dans l'exemple, "shapes" est un tableau. Est-ce que "$redact" va analyser tous les objets du tableau "shapes" ? Comment cela sera-t-il bon en termes de performance ?

0 votes

Pas tout, mais le résultat de votre premier match. C'est la raison pour laquelle vous avez mis $match comme première étape d'agrégation

14voto

Vicky Points 121

La syntaxe de find dans mongodb est la suivante

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

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

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

dans ce cas, vous avez utilisé le $elemMatch dans la partie requête, alors que si vous utilisez cet opérateur dans la partie projection, vous obtiendrez le résultat souhaité. Vous pouvez écrire votre requête comme suit

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

Vous obtiendrez ainsi le résultat souhaité.

1 votes

Cela fonctionne pour moi. Cependant, il semble que "shapes.color":"red" dans le paramètre de la requête (le premier paramètre de la méthode de recherche) n'est pas nécessaire. Vous pouvez le remplacer par {} et obtenir les mêmes résultats.

2 votes

@ErikOlson Votre suggestion est correcte dans le cas ci-dessus, où nous avons besoin de trouver tous les documents qui ont la couleur rouge et d'appliquer la projection sur eux seulement. Mais disons que si quelqu'un a besoin de trouver tous les documents qui ont la couleur bleue, il doit retourner seulement les éléments de ce tableau de formes qui ont la couleur rouge. Dans ce cas, la requête ci-dessus peut être référencée par quelqu'un d'autre également

0 votes

Cela semble être le plus simple, mais je n'arrive pas à le faire fonctionner. Elle ne renvoie que le premier sous-document correspondant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X