I have a document in the following form:
{
"_id" : <id of document>,
"inventory" : [
{
"entity_id" : "<id>",
"ad_type" : "ADTYPE"
}
]
}
I'm not sure if this is possible in mongo but I am trying to construct an upsert query on the above document where I will insert an empty array if inventory doesn't exist or push to the array if inventory has one or more elements.
I tried using the findAndModify function but I don't think that will work.
db.collection.find({"_id":"value"}).forEach(
function(doc){
if(doc.hasOwnProperty('inventory'))
{
db.collection.update({"_id":doc._id},
{"$push":{"inventory": "new value"}});
}
else
{
db.collection.update({"_id":doc._id},
{"$set":{"inventory": new Array() }});
}
});
"collection" is array, "subcollection" is object.
The script adds new field "rank" to array element with index 1.
If the field doesn't exist the script creates "rank" and set value, If the field exists the script update value.
db.userCollection.update(
{ "_id": ObjectId("5a1e7aefd1928362e53c5c48"),
$and: [ { "collection.1.subcollection.id": "1234" },
{ "collection.1.rank": {"$ne": -1 }}
]
},
{ $set: { "collection.1.rank": "5" }
}
)
Related
If I have the following document in my database :
{
"_id" : MainId,
"subdoc" : [
{
"_id" : SubdocId,
"userid" : "someid",
"toupdate": false,
},
{
"_id" : SubdocId2,
"userid" : "someid2",
"toupdate": false,
}
],
"extra" : [
"extraid1",
"extraid2"
]
}
How can I update the subdocument SubdocId2 where the id (SubdocId2) must match and either SubdocId2's userid is "someid2" OR value "extraid1" exists in "extra"?
The farthest I got is:
db.coll.update({
"subdoc._id":"SubdocId2", {
$or: ["extra":{$in:["extraid1"]}, "subdoc.userid":"someid2"]
}
}, {"subdoc.$.toupdate":true})
Maybe I forgot to quote up something, but I get an error (SyntaxError: invalid property id)
Please try this :
db.coll.update(
{
$and: [{ "subdoc._id": SubdocId2 }, {
$or: [{ "extra": { $in: ["extraid1"] } },
{ "subdoc.userid": "someid2" }]
}] // subdoc._id && (extra || subdoc.userid)
}, { $set: { "subdoc.$.toupdate": true } })
In your query there are couple of syntax issues & also if you don't use $set in your update part - it replace the entire document with "subdoc.$.toupdate": true. Of course if you're using mongoose that's different scenario as mongoose will internally does add $set but when executing in shell or any client you need to specify $set. Also if SubdocId2 is ObjectId() you need to convert string to ObjectId() in code before querying database.
Match documents if a value in an array of sub-documents is greater than some value only if the same document contains a field that is equal to some value
I have a collection that contains documents with an array of sub-documents. This array of sub-documents contains a field that dictates whether or not I can filter the documents in the collection based on another field in the sub-document. This'll make more sense when you see an example of the document.
{
"_id":"ObjectId('XXX')",
"Data":{
"A":"",
"B":"-25.78562 ; 28.35629",
"C":"165"
},
"SubDocuments":[
{
"_id":"ObjectId('XXX')",
"Data":{
"Value":"XXX",
"DataFieldId":"B"
}
},
{
"_id":"ObjectId('XXX')",
"Data":{
"Value":"",
"DataFieldId":"A"
}
},
{
"_id":"ObjectId('XXX')",
"Data":{
"Value":"105",
"DataFieldId":"Z"
}
}
]
}
I only want to match documents that contain sub-documents with a DataFieldId that is equal to Z but also filter for Values that are greater than 105 only if Data Field Id is equal to Z.
Try as below:
db.collection.aggregate([
{
$project: {
_id:1,
Data:1,
filteredSubDocuments: {
$filter: {
input: "$SubDocuments",
as: "subDoc",
cond: {
$and: [
{ $eq: ["$$subDoc.Data.DataFieldId", "Z"] },
{ $gte: ["$$subDoc.Data.Value", 105] }
]
}
}
}
}
}
])
Resulted response will be:
{
"_id" : ObjectId("5cb09659952e3a179190d998"),
"Data" : {
"A" : "",
"B" : "-25.78562 ; 28.35629",
"C" : "165"
},
"filteredSubDocuments" : [
{
"_id" : "ObjectId('XXX')",
"Data" : {
"Value" : 105,
"DataFieldId" : "Z"
}
}
]
}
This can be done by using the $elemMatch operator on sub-documents, for details you can click on provided link. For your problem you can try below query by using $elemMatch which is match simpler than aggregation:
db.collectionName.find({
"SubDocuments": {
$elemMatch: {
"Data.DataFieldId": "Z" ,
"Data.Value" : {$gte: 105}
}
} })
Its working fine, I have verified it locally, one modification you required is that you have to put the value of SubDocuments.Data.Value as Number or Long as per your requirements.
I have a mongoDB orders collection, the documents of which look as follows:
[{
"_id" : ObjectId("59537df80ab10c0001ba8767"),
"shipments" : {
"products" : [
{
"orderDetails" : {
"id" : ObjectId("59537df80ab10c0001ba8767")
}
},
{
"orderDetails" : {
"id" : ObjectId("59537df80ab10c0001ba8767")
}
}
]
},
}
{
"_id" : ObjectId("5953831367ae0c0001bc87e1"),
"shipments" : {
"products" : [
{
"orderDetails" : {
"id" : ObjectId("5953831367ae0c0001bc87e1")
}
}
]
},
}]
Now, from this collection, I want to filter out the elements in which, any of the values at shipments.products.orderDetails.id path is same as value at _id path.
I tried:
db.orders.aggregate([{
"$addFields": {
"same": {
"$eq": ["$shipments.products.orderDetails.id", "$_id"]
}
}
}])
to add a field same as a flag to decide whether the values are equal, but the value of same comes as false for all documents.
EDIT
What I want to do is compare the _id field the the documents with all shipments.products.orderDetails.id values in the array.
If even 1 of the shipments.products.orderDetails.ids match the value of the _id field, I want that document to be present in the final result.
PS I am using MongoDB 3.4, and have to use the aggregation pipeline.
Your current attempt fails because the notation returns an "array" in comparison with a "single value".
So instead either use $in where available, which can compare to see if one value is "in" an array:
db.orders.aggregate([
{ "$addFields": {
"same": {
"$in": [ "$_id", "$shipments.products.orderDetails.id" ]
}
}}
])
Or notate both as arrays using $setIsSubset
db.orders.aggregate([
{ "$addFields": {
"same": {
"$setIsSubset": [ "$shipments.products.orderDetails.id", ["$_id"] ]
}
}}
])
Where in that case it's doing a comparison to see if the "sets" have an "intersection" that makes _id the "subset" of the array of values.
Either case will return true when "any" of the id properties within the array entries at the specified path are a match for the _id property of the document.
I have a sample document like shown below
{
"_id" : "docID",
"ARRAY" : [
{
"k" : "value",
"T" : "20:15:35",
"I" : "Hai"
},
{
"K" : "some value",
"T" : "20:16:35",
"I" : "Hello"
},
{
"K" : "some other value",
"T" : "20:15:35",
"I" : "Update"
}
]
}
I am trying to update the last element in the "ARRAY" based on field "ARRAY.T"(which is only field i know at the point of update), but what my problem is first element in the array matches the query and its ARRAY.I field is updated.
Query used to update:
db.collection.update( { _id: "docID","ARRAY.T" : "20:15:35"},
{ $set: { "ARRAY.$.I": "Updated value" }
})
Actually i don't know index of the array where to update so i have to use ARRAY.I in the query, is there any way to to tell Mongodb to update the first element matched the query from last of the array.
I understand what you are saying in that you want to match the last element in this case or in fact process the match in reverse order. There is no way to modify this and the index stored in the positional $ operator will always be the "first" match.
But you can change your approach to this, as the default behavior of $push is to "append" to the end of the array. But MongoDB 2.6 introduced a $position modifier so you can in fact always "pre-pend" to the array meaning your "oldest" item is at the end.
Take this for example:
db.artest.update(
{ "array": { "$in": [5] } },
{ "$push": { "array": { "$each": [5], "$position": 0 } }},
{ "upsert": true }
)
db.artest.update(
{ "array": { "$in": [5] } },
{ "$push": { "array": { "$each": [6], "$position": 0 } }},
{ "upsert": true }
)
This results in a document that is the "reverse" of the normal $push behavior:
{ "_id" : ObjectId("53eaf4517d0dc314962c93f4"), "array" : [ 6, 5 ] }
Alternately you could apply the $sort modifier when updating your documents in order to "order" the elements so they were reversed. But that may not be the best option if duplicate values are stored.
So look into storing your arrays in "reverse" if you intend to match the "newest" items "first". Currently that is your only way of getting your "match from last" behavior.
I have a collection with a location field that was entered in the wrong order:
location: [38.7633698, -121.2697997]
When I try to place a 2d index on the field using ...
db.collection.ensureIndex({'location': '2d'});
... I get the following error because the latitude and longitude are reversed.
"err" : "location object expected, location array not in correct format",
"code" : 13654
How can I reverse this array for each document in the mongo shell?
db.loc.find().forEach(function (doc) {
var loc = [ doc.location[1], doc.location[0] ];
db.loc.update(doc, { $set: { location: loc } });
})
Starting from MongoDB 3.4 we can use the $reverseArray operator to do this beautifully.
Reverse the array:
db.collection.aggregate(
[
{ "$project": { "location": { "$reverseArray": "$location" } } }
]
)
which yields:
{
"_id" : ObjectId("576fdc687d33ed2f37a6d527"),
"location" : [ -121.2697997, 38.7633698 ]
}
Update all documents
To update all the documents in your collection, you have a couple of options.
The first is to add a $out stage to your pipeline and replace the old collection. In this case, you will need to explicitly include all the other field in the $projection stage. The $out stage look like this:
{ "$out": "collection" }
Use $push
to reserve the array using the $each & $sort functionality of it
db.getCollection('locations').updateMany({},
{
$push: {
'geo_point.coordinates': { $each: [ ], $sort: -1 }
}
});