Query collection containing BsonArray - arrays

Sorry, first time trying mongo.
Given the following data ...
db.masterList.findOne()
{
"_id" : ObjectId("59d128805b19310ac8ab3fc2"),
"MasterDefinition" : {
"Location" : [
"Whole House",
"Master Bedroom",
"Hallway 2"
],
"DeviceType" : [
"Receptacle",
"GFI",
"LED dimmer"
],
"Style" : [
"Decora",
"Standard"
],
"Color" : [
"White",
"Light Almond"
]
}
}
How do I retrieve the contents of the Color array? I expect something like
["White","Light Almond"]
How do I list the 4 arrays directly subordinate to MasterDefintion? I expect to see
["Location","DeviceType","Style","Color"]
Thanks

For the first part, you can simply do a
collection.aggregate({
$project: {
"_id": 0, // exclude the "_id" field from the result
"result": "$MasterDefinition.Color"
}
})
The second part requires a little magic (documentation can be found here: aggregation framework, $project, $objectToArray):
collection.aggregate({
$project: {
"temp": {
$objectToArray: "$MasterDefinition" // transform the "MasterDefinition" subdocument into an array
}
}
}, {
$project:{
"_id": 0, // do not include the "_id" field in the result - this is an optional step
"result": "$temp.k" // only get the keys (as in "k" fiels) from the array
}
})

How do I retrieve the contents of the Color array? I expect something like ["White","Light Almond"]
// the first argument here is a filter, the second argument is a projection
// since you specified no filter I have only included a projection
// this projection tells MongoDB to return the Color subdocument
// from within the MasterDefinition sub document
db.getCollection('masterList').find({}, {'MasterDefinition.Color': 1})
The above command will return:
{
"_id" : ObjectId("59d128805b19310ac8ab3fc2"),
"MasterDefinition" : {
"Color" : [
"White",
"Light Almond"
]
}
}
How do I list the 4 arrays directly subordinate to MasterDefintion? I expect to see ["Location","DeviceType","Style","Color"]
This is a bit trickier because "Location","DeviceType","Style","Color" are not elements in an array instead they are the names of attributes in the MasterDefinition subdocument. You can use the $objectToArray aggregation operator to turn these attribute names into an array but the resulting document doesn't looks exactly like what you hoped for. Here's an example ...
db.getCollection('masterList').aggregate([
// creates an array named "categories" from the attributes of the MasterDefinition sub document
{ $project: { categories: { $objectToArray: "$MasterDefinition" } } },
// projects on the keys of the "categories" array
{$project: {'categories.k': 1}}
])
... which produces this output:
{
"_id" : ObjectId("59d128805b19310ac8ab3fc2"),
"categories" : [
{
"k" : "Location"
},
{
"k" : "DeviceType"
},
{
"k" : "Style"
},
{
"k" : "Color"
}
]
}

Related

Compare array within objects in an array of the same Mongo document

I have a Mongo database with multiple documents and they all contain 2 Items for in store and online locations. The difference will be that attributes within the items object may differ. I am trying to capture all documents that have differing attributes between the object items with the array.
I've tried using $expr and comparing the position of the elements such as "Items.0.Attributes" == "Items.1.Attributes" but had no luck.
{
"ID" : "123456789",
"Items" : [
{
"ItemDept" : "softLines",
"ProductId" : {
"Name" : "shirts",
"_id" : "12345Shirts"
},
"Attributes" : [ "blue","small","mens"],
"Season" : "Summer"
"Location":"online"
}
,
{
"ItemDept" : "softlines",
"ProductId" : {
"Name" : "shirts",
"_id" : "12345Shirts")
},
"Attributes" : [ "blue","small","women"],
"Season" : "Summer"
"Location":"stores"
}
]
}
If I understand what you want to find/output, here's one way you could do it.
db.collection.find({
"$expr": {
"$not": {
"$setEquals": [
{"$first": "$Items.Attributes"},
{"$last": "$Items.Attributes"}
]
}
}
})
Try it on mongoplayground.net.

Mongodb find comparing diffrent elements from the same array

I have an array with say two elements, want to compare element[0]and element[1], return true if element[0].code== element[1].code
"SourceArray" : [
{
"source" : "Acetone",
"code" : "90915"
},
{
"source" : "Ketone",
"code" : "90915"
}
Use $expr with aggregation expressions, like:
db.collection.find({
$expr: {
$eq: [
"$SourceArray.0.code",
"$SourceArray.1.code"
]
}
})

Trying to filter values of an array within an array in MongoDB

I am new to MongoDB and I'm trying to filter values of an array within an array. An example of the schema is below. The schema is basically a dump of a 3 tiered Dictionary with a simple object of scalars as the leaf node.
The "I" member contains an array of documents (outer array) of key-value pairs with a string key (k), and the value (v) is an array of documents (middle array) of key-value pairs with a date as the key and value is another dictionary, which isn't part of this question.
Basically, what I need to do is retrieve the most recent data from the middle array (Date, key-value) for a given value of the outer array (string, key-value).
(Collection Sample)
{
"_id" : ObjectId("5eacfbe62758834aefdec003"),
"UserId" : UUID("46942978-29f4-4521-9932-840cead6743e"),
"Data" : {
"I" : [
{
"k" : "LRI39",
"v" : [
{
"k" : ISODate("2020-03-11T20:24:41.591Z"),
"v" : [
{
"k" : ISODate("2020-03-11T20:24:41.594Z"),
"v" : {
"Source" : 1,
"Value" : 19
}
}
]
},
{
"k" : ISODate("2020-01-22T11:37:23.393Z"),
"v" : [
{
"k" : ISODate("2020-01-22T11:37:23.412Z"),
"v" : {
"Source" : 1,
"Value" : 20
}
}
]
}
]
},
...
]
}
}
I have been able to generate a document which is basically what you see from "Data" to the end of the sample, being the entire record for LRI39, using:
db.threetier.aggregate([
{
$project: {
"Data.I": {
$filter: {
input: "$Data.I",
as: "item",
cond: {
$eq: [ "$$item.k", "LRI39" ]
}
}
}
}
}
])
However, no matter what I do, I cannot seem to return any subset of the records of the middle array: I get the 2020-03-11 and 2020-01-22 elements or I get nothing.
I have tried adding stages like the below to the projection above, figuring that I would get 1 record (the 2020-01-22 record) but I get both. If I change the date to be in 2019, I get nothing (as expected).
$project: {
"Data.I.v": {
$filter: {
input: "$Data.I.v",
as: "stamp",
cond: { $lt: [ "$$stamp.k", ISODate("2020-02-14T00:00:00Z") ] }
}
}
}
I have also tried:
{ $match: { $expr: { $lt: [ "Data.I.v.k", ISODate("2020-02-14T00:00:00Z") ] } } }
but that returns no results at all (probably because $match works on documents not arrays) as well as trying to unwind the array using $unwind: "$Data.I.v" before the match, but that returns nothing as well.
It seems like I am missing something fundamental here. I do realize that Mongo is designed (I think) to have those array items as documents, but I do want to see if this will work as is.
You will need to unwind both Data.I and Data.I.v, so that you can consider each of the sub-elements separately.
Then reverse sort by the date field.
Group by the _id and key, selecting only the first document in each group.
Finally, replaceRoot so the return is just the selected document.
db.collection.aggregate([
{$unwind: "$Data.I"},
{$unwind: "$Data.I.v"},
{$sort: {"Data.I.v.k": -1}},
{$group: {
_id: {
_id: "$_id",
key: "$Data.I.k"
},
document: {$first: "$$ROOT"}
}},
{$replaceRoot: {newRoot: "$document"}}
])
Playground

How to delete an element from an array that is nested inside of another array using MongoDB

The Problem
Suppose I have a document as follows:
doc = {
"_id" : ObjectId("56464c726879571b1dcbac79"),
"food" : {
"fruit" : [
"apple",
"orange"
]
},
"items" : [
{
"item_id" : 750,
"locations" : [
{
"store#" : 13,
"num_employees" : 138
},
{
"store#" : 49,
"num_employees" : 343
}
]
},
{
"item_id" : 650,
"locations" : [
{
"store#" : 12,
"num_employees" : 22
},
{
"store#" : 15,
"num_employees" : 52
}
]
}
]
}
I would like to delete the element
{'#store#' : 12, 'num_employees' : 22}
but only if the following conditions are true:
food.fruit contain the values apple or orange
item_id has the id 650
My Attempted Solution
I tried the following:
db.test.update({"food.fruit" : {"$in" : ["apple", "orange"]}, "items.item_id":650},{$pull:{'items.$.locations':{'store#':12,'num_employees':22}}})
The update does not work. Interestingly, if the $in operator part of the query is removed, it works. I'm using MongoDB v3.0.6 and consulted the MongoDB manual for the use of $(update):
https://docs.mongodb.org/manual/reference/operator/update/positional/
The docs contain a passage of interest:
Nested Arrays
The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value
My query, of course, traverses more than one array. Indeed, if I remove 'food.fruit' : {$in : ['apple']} from the query, it works. However, this does
not solve my problem, because of course, I need that query. I'm looking for a solution that preferably:
Does NOT require a schema change
Executes in one query/update statement
If you need to match more than one possible value in "food.fruit" and therefore other more than one possible document ( the only case where this makes sense ) then you can always replace your $in with JavScript logic in $where:
db.test.update(
{
"items.item_id": 650,
"$where": function() {
return this.food.fruit.some(function(el) {
return ["apple","orange"].indexOf(el) != -1;
});
}
},
{ "$pull": { "items.$.locations": { "store#": 12 } } },
{ "multi": true }
)
Which essentially applies the same test, though not as efficiently as "food.fruit" values cannot be tested in an index, but hopefully the other field of "items.item_id" is a sufficient match at least to not make this a real problem.
On the other hand, testing this against a MongoDB server version 3.1.9 ( development series ), the following works without problem:
db.test.update(
{ "food.fruit": { "$in": ["orange","apple"] }, "items.item_id": 650 },
{ "$pull": { "items.$.locations": { "store#": 12 } } },
{ "multi": true }
)
I would also suggest though that if you intend to include _id in your query, then you are only matching a single document anyway, and as such you need only supply the match on the array you wish to $pull from:
db.test.update(
{ "_id": 123, "items.item_id": 650 },
{ "$pull": { "items.$.locations": { "store#": 12 } } }
)
Which is fairly simple and provides no conflict, unless you really need to be sure that the required "food.fruits" values are actually present in order to apply the update. In which case, follow the former examples.
Matt,
I used your sample data and the following query works:
db.pTest.update(
{"food.fruit" : "apple","items.item_id":650},
{$pull:{'items.$.locations':{'store#':12,'num_employees':22}}}
)
No need for the $in(unless your inputting more than one value for array) nor the $elemMatch. Also for two-level deep arrays you can use {$pull: {"someArray.$.someNestedArray": {"key": "value to delete"}}}.
What you referenced from the docs.
Nested Arrays
The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value.
Translates to, mean that you can't use the $ Positional Operator twice.
Sample Data:
{
"_id" : ObjectId("56464c726879571b1dcbac79"),
"food" : {
"fruit" : [
"apple",
"orange"
]
},
"items" : [
{
"item_id" : 650,
"locations" : [
{
"store#" : 12,
"num_employees" : 22
},
{
"store#" : 15,
"num_employees" : 52
}
]
}
]
}
Results in:
{
"_id" : ObjectId("56464c726879571b1dcbac79"),
"food" : {
"fruit" : [
"apple",
"orange"
]
},
"items" : [
{
"item_id" : 650,
"locations" : [
{
"store#" : 15,
"num_employees" : 52
}
]
}
]
}

MongoDB Update Array element

I have a document structure like
{
"_id" : ObjectId("52263922f5ebf05115bf550e"),
"Fields" : [
{
"Field" : "Lot No",
"Rules" : [ ]
},
{
"Field" : "RMA No",
"Rules" : [ ]
}
]
}
I have tried to update by using the following code to push into the Rules Array which will hold objects.
db.test.update({
"Fields.Field":{$in:["Lot No"]}
}, {
$addToSet: {
"Fields.Field.$.Rules": {
"item_name": "my_item_two",
"price": 1
}
}
}, false, true);
But I get the following error:
can't append to array using string field name [Field]
How do I do the update?
You gone too deep with that wildcard $. You match for an item in the Fields array, so you get a access on that, with: Fields.$. This expression returns the first match in your Fields array, so you reach its fields by Fields.$.Field or Fields.$.Result.
Now, lets update the update:
db.test.update({
"Fields.Field": "Lot No"
}, {
$addToSet: {
"Fields.$.Rules": {
'item_name': "my_item_two",
'price':1
}
}
}, false, true);
Please note that I've shortened the query as it is equal to your expression.

Resources