π Searching inside Objects in arrays
In our books, we're using the Attribute Pattern to have different attributes in our documents. As we can see in the sample doc, we have an attributes
array, containing several objects, each with the same structure:
attributes: [
{
key: 'edition',
value: '1st'
},
{
key: 'dimensions',
value: 'Height: 1.111 Inches, Length: 1.111 Inches, Weight: 0.2 Pounds, Width: 1.111 Inches'
},
{
key: 'isbn13',
value: '9780765342508'
},
{
key: 'msrp',
value: 6.99
},
{
key: 'isbn',
value: '0765342502'
},
{
key: 'isbn10',
value: '0765342502'
}
],
Matching object fields, the simplest wayβ
How do we search for all the books that have an msrp of 9.99? We want books that, inside attributes
, have an object with key msrp
and value 9.99
?
- Atlas UI
- MongoDB Shell
[
{ $match: {"attributes.key": "msrp", "attributes.value": 9.99} },
{ $project: {_id: 0, title: 1, year: 1, totalInventory: 1, available: 1, "attributes": 1} }
]
db.books.aggregate([
{ $match: {"attributes.key": "msrp", "attributes.value": 9.99} },
{ $project: {_id: 0, title: 1, year: 1, totalInventory: 1, available: 1, "attributes": 1} }
]);
Above example is using the shorthand $and operator, that we can also explicitly write:
- Atlas UI
- MongoDB Shell
[
{ $match: { $and: [{"attributes.key": "msrp", "attributes.value": 9.99}] } },
{ $project: {_id: 0, title: 1, year: 1, totalInventory: 1, available: 1, "attributes": 1} }
]
db.books.aggregate([
{ $match: {$and: [{"attributes.key": "msrp", "attributes.value": 9.99}]} },
{ $project: {_id: 0, title: 1, year: 1, totalInventory: 1, available: 1, "attributes": 1} }
]);
Find all the books with an MSPR of 9.99 and that have been reprinted (hint: edition
is Reprint
)
Answer
- Atlas UI
- MongoDB Shell
[
{ $match:
{$and: [
{"attributes.key": "msrp", "attributes.value": 9.99},
{"attributes.key": "edition", "attributes.value": "Reprint"}]
}
},
{ $project: {_id: 0, title: 1, year: 1, totalInventory: 1, available: 1, "attributes": 1} }
]
db.books.aggregate([
{ $match:
{$and: [
{"attributes.key": "msrp", "attributes.value": 9.99},
{"attributes.key": "edition", "attributes.value": "Reprint"}]
}
},
{ $project: {_id: 0, title: 1, year: 1, totalInventory: 1, available: 1, "attributes": 1} }
]);
$unwindβ
This is OK, but we get all attributes, although we're only interested in the MSRP!
{
"_id": "0002005018",
"title": "Clara Callan: A novel",
"attributes": [
{
"key": "edition",
"value": "1st"
},
{
"key": "dimensions",
"value": "Height: 11.11 Inches, Length: 6.11 Inches, Weight: 1 Pounds, Width: 1.11 Inches"
},
{
"key": "isbn13",
"value": "9780002005012"
},
{
"key": "msrp",
"value": "0.00"
},
{
"key": "isbn",
"value": "0002005018"
},
{
"key": "isbn10",
"value": "0002005018"
}
]
}
To improve, we'll use $unwind
:
- Atlas UI
- MongoDB Shell
[
{ $unwind : "$attributes" },
{ $match: {"attributes.key": "msrp", "attributes.value": 9.99} },
{ $project: {title: 1, attributes: 1} }
]
db.books.aggregate([
{ $unwind : "$attributes" },
{ $match: {"attributes.key": "msrp", "attributes.value": 9.99} },
{ $project: {title: 1, attributes: 1} }
]);
Here we're getting a copy of each book for each object inside the $attributes
array. This "flattens" the array and returns many copies of the same documents, one for each different attribute that we have.
π To better understand $unwind
, run this aggregation.
- Atlas UI
- MongoDB Shell
You need to select the books
collection.
[
{ $match: {_id: "0395623650"} },
{ $unwind : "$attributes" },
]
db.books.aggregate([
{ $match: {_id: "0395623650"} },
{ $unwind : "$attributes" },
]);
You should get one document per attribute of the original book. All fields in these returned documents should be the same, except the ones in attributes.