I have a mongo collection workspaces in my MongoDB which consist of records that looks like as follows:
{
"_id" : ObjectId("58bdc4a13504f5ed743ad025"),
"name" : "My workspace",
"user_id" : ObjectId("58b6cf53988a874af070fd3b"),
"uploads" : [
{
"original" : "DE-20__450_GG_5002_N23_994__0136.tif"
},
{
"original" : "DE-20__450_GG_5002_N23_994__0134.png"
},
{
"original" : "DE-20__450_GG_5002_N23_994__0136.png"
},
{
"original" : "DE-20__450_GG_5002_N23_994__0134.tif"
}
]
}
Now my objective is that i want to append something in one of record of array uploads with the match criteria of original field value. For example i want to append "pseg" : "DE-20__450_GG_5002_N23_994__0136.pseg.tif" to the {"original" : "DE-20__450_GG_5002_N23_994__0136.tif"} collection in following way:
{
"_id" : ObjectId("58bdc4a13504f5ed743ad025"),
"name" : "Objective",
"user_id" : ObjectId("58b6cf53988a874af070fd3b"),
"uploads" : [
{
"original" : "DE-20__450_GG_5002_N23_994__0136.tif",
"pseg" : "DE-20__450_GG_5002_N23_994__0136.pseg.tif"
},
{
"original" : "DE-20__450_GG_5002_N23_994__0134.png"
},
{
"original" : "DE-20__450_GG_5002_N23_994__0136.png"
},
{
"original" : "DE-20__450_GG_5002_N23_994__0134.tif"
}
]
}
i tried using $elemMatch and then i added $addToSet in following way:
db.getCollection('workspaces').update({"_id": ObjectId("58bdc4a13504f5ed743ad025"),"uploads": {$elemMatch: {"original": "DE-20__450_GG_5002_N23_994__0136.tif"}}},{$addToSet:{"uploads.$": {"pseg" : "DE-20__450_GG_5002_N23_994__0136.pseg.tif"}}})
but it gives me following error:
Cannot apply $addToSet to a non-array field. Field named '0' has a non-array type object in the document _id: ObjectId('58bdc4a13504f5ed743ad025')
I think i crafted the query wrong can anyone tell me what is wrong and how can i solve it?
Sorry for my bad english :) hope you understood what i said.
Here is the correct syntax. You have to use $set with postional operator to reach the element.
db.getCollection('workspaces').update(
{"_id": ObjectId("58bdc4a13504f5ed743ad025"),"uploads": {$elemMatch: {"original": "DE-20__450_GG_5002_N23_994__0136.tif"}}},
{$set:{"uploads.$.pseg" : "DE-20__450_GG_5002_N23_994__0136.pseg.tif"}})
More information here https://docs.mongodb.com/manual/reference/operator/update/positional/#update-documents-in-an-array
You can simplify your query criteria. You don't need to use $elemMatch operator for single query condition. You can use dot notation.
db.getCollection('workspaces').update(
{"_id": ObjectId("58bdc4a13504f5ed743ad025"),"uploads.original": "DE-20__450_GG_5002_N23_994__0136.tif"},
{$set:{"uploads.$.pseg" : "DE-20__450_GG_5002_N23_994__0136.pseg.tif"}})
More information here
https://docs.mongodb.com/manual/reference/operator/query/elemMatch/#single-query-condition
Related
My JSON:
{
"user_id" : "1000" ,
"boxes" : [
{
"box_id" : "12345",
"box_name" : "Box 3",
"items" : [],
"visible" : false
},
{
"box_id" : "2000",
"box_name" : "Box 1",
"items" : [],
"visible" : true
},
{
"box_id" : "3000",
"box_name" : "Box 2",
"items" : [],
"visible" : true
}
],
"user_name" : "Jimmy",
}
I'm just trying to get which box belongs to user_id:"1000", which one's visible: true and which one's box_id: "3000".
My query is:
db.getCollection("users")
.find({user_id:"1000",
$and: [{"boxes.box_id": "3000"},{"boxes.visible": true}]},
{"boxes.$":1})
But I'm always getting box_id:12345 one. I couldn't find anything about it.
Thank you for answers.
According to the documentation for the positional $ operator, you can't use the $ with multiple conditions. You have to use $elemMatch instead to work around the limitation.
Array Field Limitations
MongoDB requires the following when dealing
with projection over arrays:
Only one positional $ operator may appear in the projection document.
Only one array field, the one being limited with the $ projection
operator, should appear in the query document. Additional array fields
in the query document may lead to undefined behavior.
The query
document should only contain a single condition on the array field
being projected. Multiple conditions may override each other
internally and lead to undefined behavior.
Under these requirements, the following query is incorrect:
db.collection.find( { <array>: <value>, <someOtherArray>: <value2> },
{ "<array>.$": 1 } )
To specify criteria on multiple fields of documents inside that array,
use the $elemMatch query operator. The following query returns the
first document inside a grades array that has a mean of greater than
70 and a grade of greater than 90.
db.students.find( { grades: { $elemMatch: {
mean: { $gt: 70 },
grade: { $gt:90 }
} } },
{ "grades.$": 1 } )
You must use the $elemMatch operator if you need separate conditions
for selecting documents and for choosing fields within those
documents.
So I would try a query like:
db.users.find({ user_id:"1000", boxes: { $elemMatch: { "box_id": "3000", "visible": true } } }, {"boxes.$":1});
I am new to MongoDB and would like to use Meteor Templates to display the queried data in a table.
I have a collection named "infoData" with this structure:
First Document:
{
"_id" : "A-89273498720",
"myItems" : [
{
"itemId" : "item_1",
"username" : "Homer",
"purpose" : "justBecause",
},
{
"itemId" : "item_2",
"username" : "March",
"purpose" : "justBecause2",
},
{
"itemId" : "item_3",
"username" : "Maggie",
"purpose" : "justBecause3",
}
]
}
Second Document:
{
"_id" : "B-564548461117",
"myItems" : [
{
"itemId" : "item_4",
"username" : "Lisa",
"purpose" : "justBecause4",
},
{
"itemId" : "item_5",
"username" : "Lisa",
"purpose" : "justBecause5",
},
{
"itemId" : "item_6",
"username" : "Bart",
"purpose" : "justBecause5",
}
]
}
Now I need to retrieve "itemId" as well as "username" and "purpose" with the "itemId" as a query operator. The "itemId" is unique. My first problem is to get the data. For example I tried this to get the single "itemId" field "item_2":
infoData.findOne({"myItems.itemId": "item_2"}, {_id: 0, 'myItems.$': 1})
which gets the same result as
infoData.findOne(
{
'myItems.itemId': "item_2"
},
{
'_id': 0, 'myItems': {$elemMatch: {'itemId': "item_2"}}
})
I am unsure if this is the result I need, because when I put this query in JSON.stringify() to see the data in the console I see all fields and objects within the array of the doc which contains "item_2" and not only the data "item_2" of the field "itemId". A possibility to get all fields "itemId", "username" and "purpose" of the query (only the object containing item_2) so that I can iterate over it later in a table would also work for me.
The second issue is that I need to display the data in a table.
So I wrote this helper:
'itemInfoDisplay': function() {
if (Meteor.userId()) {
var itemInfos= infoData.findOne(
{"myItems.itemId": "item_2"}, {_id: 0, 'myItems.$': 1});
return itemInfos
}
}
and want to show the data in a table:
{{#each itemInfoDisplay}}
{{#each myItems}}
<tr>
<td><h4>{{ itemId }}</h4></td>
</tr>
{{/each}}
{{/each}}
I know there is something wrong with the code and also with the HTML template. I guess that the result of the mongoDB query is not an array? Is it possible to make it to one? Is the used mongo query the correct solution for my needs?
At the end I just need to get all data which is assigned to a specific "itemId" and display it in a table. I would appreciate anything that helps.
I can think of two ways to do this. The first is to use a projection as mentioned in this post: Retrieve only the queried element in an object array in MongoDB collection
However, it appears you're using that $elemMatch format. If it's not working, not sure if you want to try find() rather than findOne(). There may be a difference in the way Meteor is sending the query to mongodb.
If that doesn't work, the second way is a little bit of a hack but will work. We take advantage of the fact that findOne (unlike find) is synchronous on the client. So we can get the record, then manually do a forEach to only get the appropriate array elements and return that array. Something like this:
var itemInfos= infoData.findOne({"myItems.itemId": "item_2"}, {_id: 0, 'myItems.$': 1});
var items = [];
itemInfos.myItems.forEach(function(myItem) {
if (myItem.itemId == "item_2") {
items.push(myItem);
};
});
return items;
If you use lodash, you can skip the forEach loop and use filter:
return( _.filter(itemInfos.myItems, {itemId: "item_2"}) );
The ideal way would be to restrict the data at the query level, but if that doesn't work option 2 should.
The document structure is as follows:
{
"_id" : "V001-99999999",
"vendor_number" : "V001",
"created_time" : ISODate("2016-04-26T22:15:34Z"),
"updated_time" : ISODate("2016-06-07T21:45:46.413Z"),
"items" : [
{
"sku" : "99999999-1",
"status" : "ACTIVE",
"listing_status" : "LIVE",
"inventory" : 10,
"created_time" : ISODate("2016-05-14T22:15:34Z"),
"updated_time" : ISODate("2016-05-14T20:42:21.753Z"),
},
{
"sku" : "99999999-2",
"status" : "INACTIVE",
"listing_status" : "LIVE",
"inventory" : 10,
"created_time" : ISODate("2016-04-26T22:15:34Z"),
"updated_time" : ISODate("2016-06-06T20:42:21.753Z"),
}
]
}
I want to obtain the sku from the item, the conditions are:
1) "vendor_number" = "XXX"
2) items.status = "ACTIVE" AND items.updated_time < [given_date]
Result example:
"sku" : "99999999-2"
or csv:
"sku","99999999-2"
Thank you for your support.
This should be what you want. Although I'm assuming you wanted "status": "active"?
db.getCollection('collection').aggregate([
{ $match: { "vendor_number": "XXXX" } },
{ $project: {
"items": {
$filter: {
input: "$items",
as: "item",
cond: { $eq: ["$$item.status", "ACTIVE"] } // or maybe ["$$item.listing_status", "LIVE"] ?
}
}
}
},
{ $project: { "items.sku": true } }
])
I love using aggregation to manipulate stuff. It's great all the things you can do with it. So here's what's going on:
The first part is simple. The $match step in the aggregation pipeline says just give me documents where vendor_number is "XXXX".
The next part is a bit hairy. The first projection step creates a new field, called "items", I could have called it "results" or "bob" if I wanted to. The $filter specifies which items should go into this new field. The new "items" field will be an array that will have all the results from the previous items field, hence the input: "$items", where you're using the keyword "item" to represent each input item that comes into the filter. Next, the condition says, for each item, only put it in my new "items" array if the item's status is "ACTIVE". You can change it to ["$$items.listing_status", "LIVE"] if that's what you needed. All of this will pretty much give you you're result.
The last project just get's rid of all other fields except for items.sku in each element in the new "items" array.
Hope this help. Play around with it and see what else you can do with the collection and aggregation. Let me know if you need any more clarification. If you haven't used aggregation before, take a look at the aggregation docs and the list of pipeline operators you can use with aggregation. Pretty handy tool.
This question already has answers here:
How to Update Multiple Array Elements in mongodb
(16 answers)
Closed 6 years ago.
I have a collection with objects like this
{
"_id" : ObjectId("5742be02289512cf98bf63e3"),
"name" : "test1",
"attributes" : [
{
"name" : "x",
"color" : "0xd79c9c",
"_id" : ObjectId("5742be02289512cf98bf63e8")
},
{
"name" : "y",
"color" : "0xd79c9c",
"_id" : ObjectId("5742be02289512cf98bf63e7")
},
{
"name" : "z",
"color" : "0xd79c9c",
"_id" : ObjectId("5742be02289512cf98bf63e6")
}
],
"__v" : 6
}
And I want to update all documents, and set for each attribute new field.
So I want to run a single query to update all documents at once. I think, this query will do
db.spaces.update({}, { $set: { "attributes.0.weight": 2 } }, {multi: true})
But when I run this query, I get an error:
"code" : 16837,
"errmsg" : "The positional operator did not find the
match needed from the query. Unexpanded update: attributes.$.weight"
I can't understand why.
Please help
You need to include the array field as part of the query document in order to use the positional operator.
For example, if you want to update the first array element i.e. with { "attributes.name": "x" } then you could follow the pattern:
db.spaces.update(
{ "attributes.name": "x" }, // <-- the array field must appear as part of the query document.
{ "$set": { "attributes.$.weight": 2 } },
{ "multi": true }
)
For the newer MongoDB versions 3.2.X, you could use the updateMany() method to update multiple documents within the collection based on the filter above.
The positional operator needs a match, from the match part of your update query.
for example:
db.spaces.update({ "attributes.name": "x" }, { $set: { "attributes.0.weight": 2 } }, {multi: true})
here the first parameter for update operation will match the array attributes where any element has a property name=="x", for any element that matches the condition the position operator can be used to update it.
So, because name='x', in this case the first matching element would be,
{
"name" : "x",
"color" : "0xd79c9c",
"_id" : ObjectId("5742be02289512cf98bf63e8")
},
and it will get updated.
Now from your question I understand you want to update the document in a way that in each document your first element, attribute gets a new value for weight=2.
you can do something like
db.spaces.update({ "attributes.name": { $regex: /^(?=[\S\s]{10,8000})[\S\s]*$/ } }, { $set: { "attributes.0.weight": 2 } }, {multi: true})
What we do here is match all element in array attribute. and we use the positional operator to update the first element of that array
I have a collection with the following document:
{ "_id" : "DzQuhq22NhQm5waCm", "title" : "Test", "users" : [ "wCxEmesi2M73dLze4" ], "userId" : "htMZEZTMxsQXq6sRD", "createdAt" : ISODate("2015-10-05T12:09:34.852Z") }
The attribute users is an array which contains also userIds.
Now I want to find this document if my value is either within the users array or matches the userId. So, this document should be returned if my value is htMZEZTMxsQXq6sRD or wCxEmesi2M73dLze4.
I tried the following:
db.test.find({userId: val}, {users: {$in: val}); but this does not work.
Thanks in advance.
Try this:
db.getCollection('TESTCOLLECTION').find({$or:[{'userId':'htMZEZTMxsQXq6sRD '},{'users':'wCxEmesi2M73dLze4'}]})