Récupérer uniquement l'élément demandées dans un tableau d'objets dans la collection MongoDB
-
10-10-2019 - |
Question
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"
}
]
}
Do requête:
db.test.find({"shapes.color": "red"}, {"shapes.color": 1})
ou
db.test.find({shapes: {"$elemMatch": {color: "red"}}}, {"shapes.color": 1})
retour document trouvé (Document 1) , mais toujours avec tous les éléments du tableau dans shapes
:
{ "shapes":
[
{"shape": "square", "color": "blue"},
{"shape": "circle", "color": "red"}
]
}
Cependant, je voudrais obtenir le document (Document 1) uniquement avec le tableau qui contient color=red
:
{ "shapes":
[
{"shape": "circle", "color": "red"}
]
}
Comment puis-je faire?
La solution
MongoDB 2.2 est nouvel opérateur de projection $elemMatch
fournit une autre façon de modifier le document retourné à ne contenir que les first apparié élément shapes
:
db.test.find(
{"shapes.color": "red"},
{_id: 0, shapes: {$elemMatch: {color: "red"}}});
Renvoie:
{"shapes" : [{"shape": "circle", "color": "red"}]}
Dans 2.2, vous pouvez aussi le faire en utilisant le $ projection operator
, où le $
dans un nom de domaine de l'objet de projection représente l'indice de premier élément de tableau de correspondance de la requête du champ. Les rendements en suivant 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 la nouvelle $filter
agrégation opérateur pour filtrer un réseau lors de la projection, ce qui a l'avantage d'inclure tous matches, au lieu de seulement 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"
}
]
}
]
Autres conseils
La nouvelle Agrégation Framework dans MongoDB 2.2+ offre une alternative à la carte / Reduce. L'opérateur $unwind
peut être utilisé pour séparer votre tableau de shapes
dans un flux de documents qui peuvent être matched:
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
}
Attention: Cette réponse fournit une solution qui était pertinente à ce moment-là , avant que les nouvelles fonctionnalités de MongoDB 2.2 ou ont été 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, seul le tableau entier. J'ai essayé d'utiliser le $ opérateur de position , mais cela n'a pas travail.
Le plus simple est de filtrer seulement les formes dans le client .
Si vous avez vraiment besoin la sortie correcte directement à partir de MongoDB, vous pouvez utiliser une carte-réduire 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()
Une autre façon interesing est d'utiliser expurger $ , qui est l'une des nouvelles fonctionnalités d'agrégation de MongoDB 2.6 . Si vous utilisez 2.6, vous n'avez pas besoin d'un dérouleur $ 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
"limite le contenu des documents sur la base des informations stockées dans les documents eux-mêmes" . Donc, il ne fonctionnera que à l'intérieur du document . Il scanne essentiellement votre document haut en bas, et vérifie si elle correspond à votre état de if
qui est en $cond
, s'il y a correspondre gardera soit le contenu ($$DESCEND
) ou supprimer ($$PRUNE
).
Dans l'exemple ci-dessus, les premiers retours $match
l'ensemble tableau de shapes
et $ expurger bandes vers le bas pour le résultat attendu.
Notez que {$not:"$color"}
est nécessaire, car il numérise le document de haut ainsi, et si $redact
ne trouve pas un champ de color
au niveau supérieur ce sera de retour false
qui pourrait dépouiller tout le document que nous ne voulons pas.
Mieux, vous pouvez interroger en correspondant à un élément de tableau en utilisant $slice
est-il utile de retourner l'objet important dans un tableau.
db.test.find({"shapes.color" : "blue"}, {"shapes.$" : 1})
$slice
est utile lorsque vous connaissez l'indice de l'élément, mais parfois vous voulez
selon l'élément tableau correspondant à vos critères. Vous pouvez retourner l'élément correspondant
avec l'opérateur $
.
db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1})
Outputs
{
"shapes" : [
{
"shape" : "circle",
"color" : "red"
}
]
}
La syntaxe de trouver dans MongoDB est
db.<collection name>.find(query, projection);
et la deuxième requête que vous avez écrit, qui est
db.test.find(
{shapes: {"$elemMatch": {color: "red"}}},
{"shapes.color":1})
dans ce que vous avez utilisé l'opérateur $elemMatch
dans la requête partie, alors que si vous utilisez cet opérateur dans la partie de projection alors vous obtiendrez le résultat souhaité. Vous pouvez écrire votre requête comme
db.users.find(
{"shapes.color":"red"},
{_id:0, shapes: {$elemMatch : {color: "red"}}})
Cela vous donnera le résultat souhaité.
Merci JohnnyHK .
Ici, je veux juste ajouter un peu plus complexe d'utilisation.
// 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"
}
]}
Vous avez juste besoin d'exécuter la requête
db.test.find(
{"shapes.color": "red"},
{shapes: {$elemMatch: {color: "red"}}});
résultat de cette requête est
{
"_id" : ObjectId("562e7c594c12942f08fe4192"),
"shapes" : [
{"shape" : "circle", "color" : "red"}
]
}
comme prévu il va donne le champ exact de tableau qui: Modifie les couleurs. « Rouge »
avec $ projet, il sera plus approprié d'autres éléments correspondants sages seront cotisés avec d'autres éléments dans le document.
db.test.aggregate(
{ "$unwind" : "$shapes" },
{ "$match" : {
"shapes.color": "red"
}},
{"$project":{
"_id":1,
"item":1
}}
)
db.test.find( {"shapes.color": "red"}, {_id: 0})