Mongoose - push if array else update - node.js

My schema looks like this:
{
_id: mongoose.Schema.Types.ObjectId,
timestamp: { type: Date, default: Date.now },
uid: { type: String, required: false, immutable: true },
gid: [
{
type: String
}
],
status: { type: String, required: false }
}
I want to be able to save a new object everytime uid is new and update it when it already exists. So basically saving this object:
{
uid: "xyz",
gid: "123"
}
should produce
{
_id: ObjectId("123"),
uid: "xyz",
gid: ["123"]
// note that if status is not provided it shouldn't appear here as `null`
}
Then again if this object is saved:
{
uid: "xyz",
gid: "345",
status: "new"
}
it should produce:
{
_id: ObjectId("123"),
uid: "xyz",
gid: ["123","345"]
status: "new"
}
and lastly for this object:
{
uid: "xyz",
gid: "345",
status: "old"
}
the expected result would be
{
_id: ObjectId("123"),
uid: "xyz",
gid: ["123","345"]
status: "old"
}
Is this possible to achieve in a single query? Sorry if my explenation is complicated, I don't know how else to put it.

Case1: only status changes and array exists, the below code, won't add a new element
> //actual code output from Mongo shell 4.2 on windows10 OS
> //use add to set and set update to acheive your update goals(array vs addition
> //of fields in two updates
> //add to set checks for an array element is present in array, if it is present it does nothing
> //else it will add the element in the array
> //pre-update document check
> db.ugs.find().pretty();
{
"_id" : ObjectId("5f625d864651219e25c775c1"),
"uid" : "xyz",
"gid" : [
"345",
"123",
"100",
"200"
],
"status" : "old"
}
> var v_status = "new";
> db.ugs.aggregate([
... {$match:{uid: "xyz"}}
... ]).forEach(function(doc){
... print("uid: ", doc.uid);
... print("gid: ",doc.gid);
... print("pre-status: ", doc.status);
... db.ugs.update(
... {"_id":doc._id},
... {$addToSet:{gid:"200"}
... });
... db.ugs.update(
... {"_id":doc._id},
... {$set:{status:v_status}}
... );
... print("post-status: ", v_status);
... });
uid: xyz
gid: 345,123,100,200
pre-status: old
post-status: new
> //post update document check
> db.ugs.find().pretty();
{
"_id" : ObjectId("5f625d864651219e25c775c1"),
"uid" : "xyz",
"gid" : [
"345",
"123",
"100",
"200"
],
"status" : "new"
}
>
Case2: change status as well add element if it does not exists in array
> //pre-update document check
> db.ugs.find().pretty();
{
"_id" : ObjectId("5f625d864651219e25c775c1"),
"uid" : "xyz",
"gid" : [
"345",
"123",
"100",
"200"
],
"status" : "new"
}
> var v_status = "old";
> db.ugs.aggregate([
... {$match:{uid: "xyz"}}
... ]).forEach(function(doc){
... print("uid: ", doc.uid);
... print("gid: ",doc.gid);
... print("pre-status: ", doc.status);
... db.ugs.update(
... {"_id":doc._id},
... {$addToSet:{gid:"444"}
... });
... db.ugs.update(
... {"_id":doc._id},
... {$set:{status:v_status}}
... );
... print("post-status: ", v_status);
... });
uid: xyz
gid: 345,123,100,200
pre-status: new
post-status: old
> //post update document check
> db.ugs.find().pretty();
{
"_id" : ObjectId("5f625d864651219e25c775c1"),
"uid" : "xyz",
"gid" : [
"345",
"123",
"100",
"200",
"444"
],
"status" : "old"
}
>
Case3: set and unset the value of the status field as needed.
> db.ugs.find().pretty();
{
"_id" : ObjectId("5f625d864651219e25c775c1"),
"uid" : "xyz",
"gid" : [
"345",
"123",
"100",
"200",
"444"
],
"status" : ""
}
> var v_status = "";
> db.ugs.aggregate([
... {$match:{uid: "xyz"}}
... ]).forEach(function(doc){
... print("uid: ", doc.uid);
... print("gid: ",doc.gid);
... print("pre-status: ", doc.status);
... db.ugs.update(
... {"_id":doc._id},
... {$addToSet:{gid:"444"}
... });
... db.ugs.update(
... {"_id":doc._id,status:{$exists:true}},
... {$set:{status:v_status}}
... );
... print("post-status: ", v_status);
... });
uid: xyz
gid: 345,123,100,200,444
pre-status:
post-status:
> //post update document check
> db.ugs.find().pretty();
{
"_id" : ObjectId("5f625d864651219e25c775c1"),
"uid" : "xyz",
"gid" : [
"345",
"123",
"100",
"200",
"444"
],
"status" : ""
}
//unset the field if not required, it will not show in the document
> var v_status = "";
> db.ugs.aggregate([
... {$match:{uid: "xyz"}}
... ]).forEach(function(doc){
... print("uid: ", doc.uid);
... print("gid: ",doc.gid);
... print("pre-status: ", doc.status);
... db.ugs.update(
... {"_id":doc._id},
... {$addToSet:{gid:"444"}
... });
... db.ugs.update(
... {"_id":doc._id,status:{$exists:true}},
... {$unset:{status:v_status}}
... );
... print("post-status: ", v_status);
... });
uid: xyz
gid: 345,123,100,200,444
pre-status: [unknown type]
post-status:
> //post update document check
> db.ugs.find().pretty();
{
"_id" : ObjectId("5f625d864651219e25c775c1"),
"uid" : "xyz",
"gid" : [
"345",
"123",
"100",
"200",
"444"
]
}
>

You're looking the upsert option to add a new doc if it doesn't exist already. Combined with the addToSet atomic opperator to create the array of gids.
You'll have to break the object into $set and $addToSet updaters with rest.
let saveObj = {
uid: "xyz",
gid: "123"
};
let {gid, ...setFields} = saveObj;
YourModel.updateOne(
{uid: setFields.uid},
{$set: setFields, $addToSet: {gid}},
{upsert: true}
).then(...);

Related

mongo : update property of specific element in array

I have this collection :
{
"_id" : ObjectId("5ac69e90a9d1a5f3e01a5233"),
"category": "spain",
"products" : [
{
"label" : "uno"
},
{
"label" : "dos"
},
{
"label" : "tres"
}
]
},
{
"_id" : ObjectId("5ac69e90a9d1a5f3e01a5234"),
"category": "england",
"products" : [
{
"label" : "one"
},
{
"label" : "two"
},
{
"label" : "three"
}
]
}
I want to do the following operation : update the label from "one" to "four" of the object with the category england. But I have some troubles to design the most elegant and performant solution :
first solution : I could copy paste and rewrite the entire document with just replacing the one by four
second solution where I struggle : I would like to find the element with label equals to one and updates it to four, but I don't know how to do. I don't want to use mongo path index like 'products.O.label' because I can't garantee that the product with label one will be at position 0 in the products array.
Thanks in advance
You could use this one:
db.collection.updateMany(
{ category: "england" },
{ $set: { "products.$[element].label": "four" } },
{ arrayFilters: [{ "element.label": "one" }] }
)
If you prefer and aggregation pipeline it would be this one:
db.collection.updateMany(
{ category: "england" },
[{
$set: {
products: {
$map: {
input: "$products",
in: {
$cond: {
if: { $eq: ["$$this.label", "one"] },
then: { label: "four" },
else: "$$this"
}
}
}
}
}
}]
)
but it might be an overkill, in my opinion.
Further, referring to #Wernfried Domscheit, another way using aggregation.
> db.catg1.find();
{ "_id" : ObjectId("5ac69e90a9d1a5f3e01a5233"), "category" : "spain", "products" : [ { "label" : "uno" }, { "label" : "dos" }, { "label" : "tres" } ] }
{ "_id" : ObjectId("5ac69e90a9d1a5f3e01a5234"), "category" : "england", "products" : [ { "label" : "four" }, { "label" : "two two" }, { "label" : "three" } ] }
> db.catg1.aggregate([
... {$unwind:"$products"},
... {$match:{category:"england",
... "products.label":"four"
... }
... },
... ]).forEach(function(doc){
... print(doc._id);
... db.catg1.update(
... {"_id":doc._id},
... { $set:{"products.$[element].label":"one"}},
... {arrayFilters: [{"element.label":"four"}]}
... );
... });
ObjectId("5ac69e90a9d1a5f3e01a5234")
> db.catg1.find();
{ "_id" : ObjectId("5ac69e90a9d1a5f3e01a5233"), "category" : "spain", "products" : [ { "label" : "uno" }, { "label" : "dos" }, { "label" : "tres" } ] }
{ "_id" : ObjectId("5ac69e90a9d1a5f3e01a5234"), "category" : "england", "products" : [ { "label" : "one" }, { "label" : "two two" }, { "label" : "three" } ] }
> db.version();
4.2.6
>

Querying subdocuments array with filter and return original documents with mongoose

I am using mongoose and have the following structure the documents:
{
user: {
comments: [
{ title: "mpla", active: true },
{ title: "mpla", active: false }
]
}
}
...
How can i return all my documents, but only the active comments in the comments array.
Taking a general case here where main documents may have others fields other than user and user doc itself may have other fields as well:
Sample docs:
[
{
user: {
comments: [
{ title: "mpla", active: true },
{ title: "mpla", active: false }
],
name: "abc",
gender: "male",
createdDate: new Date("2019-04-01"),
modifiedDate: new Date("2019-08-24")
},
story: {
name: "a mocking bird",
year: 1994,
cost: "$5"
}
},
{
user: {
comments: [
{ title: "nope", active: true },
{ title: "hello", active: true }
],
name: "pqr",
gender: "female",
createdDate: new Date("2019-05-01"),
modifiedDate: new Date("2019-09-24")
},
story: {
name: "a kite runner",
year: 2005,
cost: "$2"
}
}
]
Now here all fields with documents must be returned but comments array should contain active=true docs only.
Aggregation query with $filter and $project :
db.collection.aggregate([
{
$project: {
_id: 1,
story: 1,
user: {
name: 1,
gender: 1,
createdDate: 1,
modifiedDate: 1,
comments: {
$filter: {
input: "$user.comments",
as: "comment",
cond: { $eq: ["$$comment.active", true] }
}
}
}
}
}
]).pretty();
Output documents:
{
"_id" : ObjectId("5d8bb8c66926e92a334275d4"),
"user" : {
"name" : "abc",
"gender" : "male",
"createdDate" : ISODate("2019-04-01T00:00:00Z"),
"modifiedDate" : ISODate("2019-08-24T00:00:00Z"),
"comments" : [
{
"title" : "mpla",
"active" : true
}
]
},
"story" : {
"name" : "a mocking bird",
"year" : 1994,
"cost" : "$5"
}
},
{
"_id" : ObjectId("5d8bb8c66926e92a334275d5"),
"user" : {
"name" : "pqr",
"gender" : "female",
"createdDate" : ISODate("2019-05-01T00:00:00Z"),
"modifiedDate" : ISODate("2019-09-24T00:00:00Z"),
"comments" : [
{
"title" : "nope",
"active" : true
},
{
"title" : "hello",
"active" : true
}
]
},
"story" : {
"name" : "a kite runner",
"year" : 2005,
"cost" : "$2"
}
}
You will have to use mongodb aggregation, so the query will be:
db.collectionName.aggregate(
{
$unwind: $user.comments
}
)
This will decontruct the comments array and will include other fields like the id included in each document. So e.g lets say your document was:
{ "_id": 1, "user" :
{ "comments":
[ { "title": "mpla", "active" : true }, { "title": "mpla", "active" : false } }]
}
}
Once we run the above given query it will result in the following documents:
{ "_id": 1, "user" :
{ "comments": { "title": "mpla", "active" : true }
}
}
}
{ "_id": 1, "user" :
{ "comments": { "title": "mpla", "active" : false }
}
}
}
As you can see now we have two separate documents you can now query them using $match operator and group them back into an array using $group operator.
Hope that answers your question.

Need to update an element in array in mongodb

I am trying to update the store = 465 , AisleName = 59 and set AisleSort = 34 by this update query for the below code
db.getCollection('products').update({'AvailableOnStores.AisleName': { '$eq': '59' }, 'AvailableOnStores.StoreNumber': { '$eq': '465' } }, { '$set': { 'AvailableOnStores.$.AisleSort': 34 } } )
Then it is updating with 34 in the 465 store but we don't have that AisleName in that store. we have that aisle name in store 423. I cannot check element by element, as I am checking all these from json file that contains sort and AisleName for each store.
{
"ProductCode" : "6786777",
"AvailableOnStores" : [
{
"StoreNumber" : "465",
"Price" : "19",
"AisleSort" : 9,
"AisleName" : "Checkout Lane",
"AisleLocations" : [
{
"bayNumber" : 6,
"description" : "Checkout Lane",
}
]
},
{
"StoreNumber" : "423",
"Price" : "1",
"AisleSort" : 5,
"AisleName" : "59",
"AisleLocations" : [
{
"description" : " Aisle 59",
},
{
"description" : "Aisle 25",
},
{
"description" : "Aisle 4",
}
]
}
],
"NotAvailableOnStores" : [],
"IsPricingVaries" : false
}
If you want to update the document in which both of these conditions gets true you should modify your query like this
db.getCollection('products').update(
{
'AvailableOnStores':{
$elemMatch:{'AisleName':{ '$eq': '59' },'StoreNumber':{ '$eq': '465' }}}
},
{
'$set': {'AvailableOnStores.$.AisleSort': 34 }
}
)
For reference read this documentation on mongodb $elemMatch and let me know if you need more help

How to add parent name with their name using parent Id

I have following array which is saved in Database. i want to modify it to display like following which show their hierarchic with parent in localeName key.
var allLocales = [
{
id: 123,
localeName: 'Test',
parentId: null
},
{
id: 456,
localeName: 'Test 1',
parentId: 123
},
{
id: 789,
localeName: 'Test 2',
parentId: 456
}
]
I want to change above array to following array by changing their display name like this using their parents.:
allLocales = [
{
id: 123,
localeName: 'Test',
parentId: null
},
{
id: 456,
localeName: 'Test > Test 1',
parentId: 123
},
{
id: 789,
localeName: 'Test > Test 1 > Test 2',
parentId: 456
}
]
Try this aggregation if you are using mongo 3.4+
you can use $graphLookup for hierarchical queries $graphLookup
db.locales.aggregate(
[
{$graphLookup : {
from : "locales",
startWith : "$parentId",
connectFromField : "parentId",
connectToField : "id",
as : "parents"
}
},
{$addFields : {localeName : {$substr : [{$concat : [{$reduce : {input : "$parents", initialValue : "", in : {$concat : ["$$value", " > ", "$$this.localeName"]}}}, " > " ,"$localeName"] }, 3 , 1000]}}},
{$project : {parents : 0}}
]
).pretty()
collection
> db.locales.find()
{ "_id" : ObjectId("5a73dead0cfc59674782913a"), "id" : 123, "localeName" : "Test", "parentId" : null }
{ "_id" : ObjectId("5a73dead0cfc59674782913b"), "id" : 456, "localeName" : "Test 1", "parentId" : 123 }
{ "_id" : ObjectId("5a73dead0cfc59674782913c"), "id" : 789, "localeName" : "Test 2", "parentId" : 456 }
>
result
> db.locales.aggregate( [ {$graphLookup : { from : "locales", startWith : "$parentId", connectFromField : "parentId", connectToField : "id", as : "parents" } }, {$addFields : {localeName : {$substr : [{$concat : [{$reduce : {input : "$parents", initialValue : "", in : {$concat : ["$$value", " > ", "$$this.localeName"]}}}, " > " ,"$localeName"] }, 3 , 100]}}}, {$project : {parents : 0}} ] ).pretty()
{
"_id" : ObjectId("5a73dead0cfc59674782913a"),
"id" : 123,
"localeName" : "Test",
"parentId" : null
}
{
"_id" : ObjectId("5a73dead0cfc59674782913b"),
"id" : 456,
"localeName" : "Test > Test 1",
"parentId" : 123
}
{
"_id" : ObjectId("5a73dead0cfc59674782913c"),
"id" : 789,
"localeName" : "Test > Test 1 > Test 2",
"parentId" : 456
}
You need to make recursive function to solve this problem.
I made like below and tested.
Please see the function.
var allLocales = [
{ id: 123, localeName: 'Test', parentId: null },
{ id: 456, localeName: 'Test 1', parentId: 123 },
{ id: 789, localeName: 'Test 2', parentId: 456 }
];
function nameRecursion(element) {
if(element.parentId == null) {
return element.localeName
}else {
var parent = allLocales.find(item => item.id === element.parentId);
return nameRecursion(parent) + " -> " + element.localeName;
}
}
var newArray = allLocales.map(a => Object.assign({}, a));
for(var i=0; i<allLocales.length; i++){
newArray[i].localeName = nameRecursion(allLocales[i]);
}
console.log(allLocales);
console.log(newArray);

findAndModify doesn't insert full document to set

I'm running the following using nodejs native mongodb client:
db.collection("users").findAndModify(
{ _id:ObjectID.createFromHexString(id), "profiles._id":pid}, {},
{ '$addToSet' : {'profiles.$.categories': category}}, {new:true},
function(err, user){
if(err){
res.json(err);
}
}
);
I have a category I want to add to the categories set, the problem is that after the run I see a new category in the set but an items array that was part of the category is set to null and was not saved.
Meaning a category looks like that:
{
name:'n1',
items:['it1', it2']
}
A full document looks like that:
{
_id: ...
profiles: [
{
_id: ...
categories: [
{
name:'c1',
items:['it1', it2']
},
{
name:'c2',
items:['it3', it4']
}
]
}
]
}
This works as expected for me in the Mongo shell:
> var id = ObjectId(), pid = ObjectId();
> db.users.insert({
... _id: id,
... profiles: [
... {
... _id: pid,
... categories: [
... {
... name: 'c1',
... items: ['it1', 'it2']
... },
... {
... name: 'c2',
... items: ['it3', 'it4']
... }
... ]
...
... }
... ]
... });
>
> var category = {
... name:'n1',
... items:['it1', 'it2']
... };
>
>
> db.users.findAndModify({
... query: { _id: id, "profiles._id": pid},
... update: {
... '$addToSet' : {'profiles.$.categories': category}
... },
... new:true
... });
{
"_id" : ObjectId("51bfc532c0a62b206198663e"),
"profiles" : [
{
"_id" : ObjectId("51bfc532c0a62b206198663f"),
"categories" : [
{
"name" : "c1",
"items" : [
"it1",
"it2"
]
},
{
"name" : "c2",
"items" : [
"it3",
"it4"
]
},
{
"name" : "n1",
"items" : [
"it1",
"it2"
]
}
]
}
]
}

Resources