$elemMatch in array object with $in and $exist - mongodb

[{
_id: 6231cbafee8a3b08abb4ae67,
jadwal: {
_id: 6231cb8bbf823408b62f7280,
paket: [
paket : 6225ae9ac883840368c290cb
show_total_question:10
token:"TPKTGO"
},
{
paket : 616ee502a022be00116121c5
show_total_question:10
token:null
}],
},
paket: {
_id: 6225ae9ac883840368c290cb,
}
},
{
_id: 616ef62ca022be00116121df,
jadwal: {
_id: 616eee195e09f600183a267b,
paket: [
paket : 616ee3f32a30160019829bc1
show_total_question:10
},
{
paket : 616f12c7d2a514003018831f
show_total_question:10
token:"KQABIN"
}],
},
paket: {
_id: 616ee3f32a30160019829bc1,
}
},
]
First i have those two sample data from aggregation $lookup reference to jadwal and paket.
i have to filter the data like matching paket._id with jadwal.paket.paket and then tried to check if the token is not exist or have value [null,""]. Soo the output that i hope is
{
_id: 616ef62ca022be00116121df,
jadwal: {
_id: 616eee195e09f600183a267b,
paket: [
paket : 616ee3f32a30160019829bc1
show_total_question:10
},
{
paket : 616f12c7d2a514003018831f
show_total_question:10
token:"KQABIN"
}],
},
paket: {
_id: 616ee3f32a30160019829bc1,
}
}
Because in paket._id have token.value in jadwal.paket.token is not exists. The condition on this data is paket._id is always have copy or same value in jadwal.paket.paket
i use this code in bellow of my aggregation
{'jadwal.paket':{'$elemMatch':{'paket':'$pakets._id','token':{$exists:false}
but seems not working, or perhaps i do it wrong?
please help me

I got the answer
first you have to unwind the jadwal.paket
and then use $expr as a filter

Related

How to avoid adding duplicate objects to an array in MongoDB

this is my schema:
new Schema({
code: { type: String },
toy_array: [
{
date:{
type:Date(),
default: new Date()
}
toy:{ type:String }
]
}
this is my db:
{
"code": "Toystore A",
"toy_array": [
{
_id:"xxxxx", // automatic
"toy": "buzz"
},
{
_id:"xxxxx", // automatic
"toy": "pope"
}
]
},
{
"code": "Toystore B",
"toy_array": [
{
_id:"xxxxx", // automatic
"toy": "jessie"
}
]
}
I am trying to update an object. In this case I want to update the document with code: 'ToystoreA' and add an array of subdocuments to the array named toy_array if the toys does not exists in the array.
for example if I try to do this:
db.mydb.findOneAndUpdate({
code: 'ToystoreA,
/*toy_array: {
$not: {
$elemMatch: {
toy: [{"toy":'woddy'},{"toy":"buzz"}],
},
},
},*/
},
{
$addToSet: {
toy_array: {
$each: [{"toy":'woddy'},{"toy":"buzz"}],
},
},
},
{
new: false,
}
})
they are added and is what I want to avoid.
how can I do it?
[
{
"code": "Toystore A",
"toy_array": [
{
"toy": "buzz"
},
{
"toy": "pope"
}
]
},
{
"code": "Toystore B",
"toy_array": [
{
"toy": "jessie"
}
]
}
]
In this example [{"toy":'woddy'},{"toy":"buzz"}] it should only be added 'woddy' because 'buzz' is already in the array.
Note:when I insert a new toy an insertion date is also inserted, in addition to an _id (it is normal for me).
As you're using $addToSet on an object it's failing for your use case for a reason :
Let's say if your document look like this :
{
_id: 123, // automatically generated
"toy": "buzz"
},
{
_id: 456, // automatically generated
"toy": "pope"
}
and input is :
[{_id: 789, "toy":'woddy'},{_id: 098, "toy":"buzz"}]
Here while comparing two objects {_id: 098, "toy":"buzz"} & {_id: 123, "toy":"buzz"} - $addToSet consider these are different and you can't use $addToSet on a field (toy) in an object. So try below query on MongoDB version >= 4.2.
Query :
db.collection.updateOne({"_id" : "Toystore A"},[{
$addFields: {
toy_array: {
$reduce: {
input: inputArrayOfObjects,
initialValue: "$toy_array", // taking existing `toy_array` as initial value
in: {
$cond: [
{ $in: [ "$$this.toy", "$toy_array.toy" ] }, // check if each new toy exists in existing arrays of toys
"$$value", // If yes, just return accumulator array
{ $concatArrays: [ [ "$$this" ], "$$value" ] } // If No, push new toy object into accumulator
]
}
}
}
}
}])
Test : aggregation pipeline test url : mongoplayground
Ref : $reduce
Note :
You don't need to mention { new: false } as .findOneAndUpdate() return old doc by default, if you need new one then you've to do { new: true }. Also if anyone can get rid of _id's from schema of array objects then you can just use $addToSet as OP was doing earlier (Assume if _id is only unique field), check this stop-mongoose-from-creating-id-property-for-sub-document-array-items.

MongoDB How to remove value from array if exist otherwise add

I have document
{
"_id" : ObjectId("5aebf141a805cd28433c414c"),
"forumId" : ObjectId("5ae9f82989f7834df037cc90"),
"userName" : "Name",
"usersLike" : [
"1","2"
],
"comment" : "Comment",
}
I want to remove value from usersLike array if the value exists, or add if the value does not exist.
Eg:
If I try to push 1 into usersLike, it should return
{
"_id" : ObjectId("5aebf141a805cd28433c414c"),
"forumId" : ObjectId("5ae9f82989f7834df037cc90"),
"userName" : "Name",
"usersLike" : [
"2"
],
"comment" : "Comment",
}
How can I query it..??
MongoDB version 4.2+ introduces pipelined update. Which means we can now use aggregation operators while updating. this gives us a lot of power.
db.collection.updateOne(
{
_id: ObjectId("597afd8200758504d314b534")
},
[
{
$set: {
usersLike: {
$cond: [
{
$in: ["1", "$usersLike"]
},
{
$setDifference: ["$usersLike", ["1"]]
},
{
$concatArrays: ["$usersLike", ["1"]]
}
]
}
}
}
]
)
Mongodb doesn't support conditional push or pull update. However you can still do it by using find:
db.collectionName.find({_id:ObjectId("597afd8200758504d314b534"),usersLike:{$in:["1"]}}).pretty()
if id exist in usersLike than pull else push.
Or you use the update query to pull as:
db.collectionName.update({
_id: ObjectId("597afd8200758504d314b534"),
usersLike: {
$in: ["1"]
}
}, {
$pull: { 'usersLike': "1" }
}, { multi: true })
And to push you can use:
db.collectionName.update({
_id:ObjectId("597afd8200758504d314b534"),
usersLike:{$nin:["1"]
}},{
$push:{'usersLike':"1"}
}, {multi: true})
Try this :
db.collectionName.update({_id:ObjectId("597afd8200758504d314b534")},{$pull:{'usersLike':"1"}}, {multi: true})
Try this
if db.collectionName.find({'_id':ObjectId("5aebf141a805cd28433c414c"),'usersLike:{'$in:['1']}}).count() > 0:
db.collectionName.update({'_id':ObjectId("5aebf141a805cd28433c414c")},{'$pull':{'usersLike':'1'}})
else:
db.collectionName.update({'_id':ObjectId("5aebf141a805cd28433c414c")},{'$addToSet':{'usersLike':'1'}})

Mongoose aggregate issue with grouping by nested field

I have a schema that stores user attendance for events:
event:
_id: ObjectId,
...
attendances: [{
user: {
type: ObjectId,
ref: 'User'
},
answer: {
type: String,
enum: ['yes', 'no', 'maybe']
}
}]
}
Sample data:
_id: '533483aecb41af00009a94c3',
attendances: [{
user: '531770ea14d1f0d0ec42ae57',
answer: 'yes',
}, {
user: '53177a2114d1f0d0ec42ae63',
answer: 'maybe',
}],
I would like to return this data in the following format when I query for all attendances for a user:
var attendances = {
yes: ['533497dfcb41af00009a94d8'], // These are the event IDs
no: [],
maybe: ['533497dfcb41af00009a94d6', '533497dfcb41af00009a94d2']
}
I am not sure the aggegation pipeline will return it in this format? So I was thinking I could return this and modify it easily:
var attendances = [
answer: 'yes',
ids: ['533497dfcb41af00009a94d8'],
},{
answer: 'no',
ids: ['533497dfcb41af00009a94d8']
}, {
answer: 'maybe',
ids: ['533497dfcb41af00009a94d6', '533497dfcb41af00009a94d2']
}];
My attempt is not succesful however. It doesn't group it by answer:
this.aggregate({
$match: {
'attendances.user': someUserId
}
}, {
$project: {
answer: '$attendances.answer'
}
}, {
$group: {
_id: '$answer',
ids: {
$addToSet: "$_id"
}
}
}, function(e, docs) {
});
Can I return the data I need in the first desired format and if not how can I correct the above code to achieve the desired result?
On that note - perhaps the map-reduce process would be better suited?
The below query will help you get close to the answer you want. Although, it isn't exactly in the same format you are expecting, you get separate documents for each answer option and an array of event id's.
db.collection.aggregate([
// Unwind the 'attendances' array
{"$unwind" : "$attendances"},
// Match for user
{"$match" : {"attendances.user" : "53177a2114d1f0d0ec42ae63"}},
// Group by answer and push the event id's to an array
{"$group" : {_id : "$attendances.answer", eventids : {$push : "$_id"}}}
])
This produces the below output:
{
"result" : [
{
"_id" : "yes",
"eventids" : [
ObjectId("53d968a8c4840ac54443a9d6")
]
},
{
"_id" : "maybe",
"eventids" : [
ObjectId("53d968a8c4840ac54443a9d7"),
ObjectId("53d968a8c4840ac54443a9d8")
]
}
],
"ok" : 1
}

way to update multiple documents with different values

I have the following documents:
[{
"_id":1,
"name":"john",
"position":1
},
{"_id":2,
"name":"bob",
"position":2
},
{"_id":3,
"name":"tom",
"position":3
}]
In the UI a user can change position of items(eg moving Bob to first position, john gets position 2, tom - position 3).
Is there any way to update all positions in all documents at once?
You can not update two documents at once with a MongoDB query. You will always have to do that in two queries. You can of course set a value of a field to the same value, or increment with the same number, but you can not do two distinct updates in MongoDB with the same query.
You can use db.collection.bulkWrite() to perform multiple operations in bulk. It has been available since 3.2.
It is possible to perform operations out of order to increase performance.
From mongodb 4.2 you can do using pipeline in update using $set operator
there are many ways possible now due to many operators in aggregation pipeline though I am providing one of them
exports.updateDisplayOrder = async keyValPairArr => {
try {
let data = await ContestModel.collection.update(
{ _id: { $in: keyValPairArr.map(o => o.id) } },
[{
$set: {
displayOrder: {
$let: {
vars: { obj: { $arrayElemAt: [{ $filter: { input: keyValPairArr, as: "kvpa", cond: { $eq: ["$$kvpa.id", "$_id"] } } }, 0] } },
in:"$$obj.displayOrder"
}
}
}
}],
{ runValidators: true, multi: true }
)
return data;
} catch (error) {
throw error;
}
}
example key val pair is: [{"id":"5e7643d436963c21f14582ee","displayOrder":9}, {"id":"5e7643e736963c21f14582ef","displayOrder":4}]
Since MongoDB 4.2 update can accept aggregation pipeline as second argument, allowing modification of multiple documents based on their data.
See https://docs.mongodb.com/manual/reference/method/db.collection.update/#modify-a-field-using-the-values-of-the-other-fields-in-the-document
Excerpt from documentation:
Modify a Field Using the Values of the Other Fields in the Document
Create a members collection with the following documents:
db.members.insertMany([
{ "_id" : 1, "member" : "abc123", "status" : "A", "points" : 2, "misc1" : "note to self: confirm status", "misc2" : "Need to activate", "lastUpdate" : ISODate("2019-01-01T00:00:00Z") },
{ "_id" : 2, "member" : "xyz123", "status" : "A", "points" : 60, "misc1" : "reminder: ping me at 100pts", "misc2" : "Some random comment", "lastUpdate" : ISODate("2019-01-01T00:00:00Z") }
])
Assume that instead of separate misc1 and misc2 fields, you want to gather these into a new comments field. The following update operation uses an aggregation pipeline to:
add the new comments field and set the lastUpdate field.
remove the misc1 and misc2 fields for all documents in the collection.
db.members.update(
{ },
[
{ $set: { status: "Modified", comments: [ "$misc1", "$misc2" ], lastUpdate: "$$NOW" } },
{ $unset: [ "misc1", "misc2" ] }
],
{ multi: true }
)
Suppose after updating your position your array will looks like
const objectToUpdate = [{
"_id":1,
"name":"john",
"position":2
},
{
"_id":2,
"name":"bob",
"position":1
},
{
"_id":3,
"name":"tom",
"position":3
}].map( eachObj => {
return {
updateOne: {
filter: { _id: eachObj._id },
update: { name: eachObj.name, position: eachObj.position }
}
}
})
YourModelName.bulkWrite(objectToUpdate,
{ ordered: false }
).then((result) => {
console.log(result);
}).catch(err=>{
console.log(err.result.result.writeErrors[0].err.op.q);
})
It will update all position with different value.
Note : I have used here ordered : false for better performance.

Removing the minimum element of a particular attribute type in MongoDB

I have the following schemea in MongoDB:
{
"_id" : 100,
"name" : "John Doe",
"scores" : [
{
"type" : "exam",
"score" : 334.45023
},
{
"type" : "quiz",
"score" : 11.78273309957772
},
{
"type" : "homework",
"score" : 6.676176060654615
},
{
"type" : "homework",
"score" : 35.8740349954354
}
]
}
I am looking for a way to remove the homework with the least score. I have found a related answer here but, it doesn't help much as I need to find out only the 'homework' with th least score and remove it.
I am using MongoDB along with the PyMongo Driver.
you need to add match like:
myresults = scores.aggregate( [ { "$unwind": "$scores" }, { '$match': {'scores.type': "homework" } }, { "$group": { '_id':'$_id' , 'minitem': { '$min': "$scores.score" } } } ] )
for result in myresults['result']:
scores.update( { '_id': result['_id'] }, { '$pull': { 'scores': { 'score': result['minitem'] } } } )
I tried using native mongodb commands and it works.
Use the below 2 commands to make it work.
1)
cursor = db.students.aggregate([{ "$unwind": "$scores" }, { "$match": { "scores.type": "homework"}},{ "$group": {'_id': '$_id', 'minitem': {'$min':"$scores.score"}}}]), null
2)
cursor.forEach(function(coll) {db.students.update({'_id': coll._id}, {'$pull': {'scores': {'score': coll.minitem}}})})
Here is a solution using Python:
students = db.students
cursor = students.find({})
for doc in cursor:
hw_scores = []
for item in doc["scores"]:
if item["type"] == "homework":
hw_scores.append(item["score"])
hw_scores.sort()
hw_min = hw_scores[0]
students.update({"_id": doc["_id"]},
{"$pull":{"scores":{"score":hw_min}}})
I don't think it's possible using native mongodb commands. I think the best way of doing it would be to write a javascript function to drop the lowest score and run it on the server; this would have the advantage of being automic, so that a list couldn't be updated while you were removing from it, keeping things consistent.
Here's some documentation: http://docs.mongodb.org/manual/applications/server-side-javascript/
I followed the third answer here. Removing the minimum element of a particular attribute type in MongoDB
var MongoClient = require('mongodb').MongoClient;
MongoClient.connect('mongodb://localhost:27017/school', function(err, db) {
if(err) throw err;
var students = db.collection('students');
cursor = students.aggregate(
[{ "$unwind": "$scores" },
{ "$match": { "scores.type": "homework"}},
{ "$group": {'_id': '$_id', 'minitem': {
'$min': "$scores.score"
}
}
}
]);
cursor.forEach(
function(coll) {
students.update({
'_id': coll._id
}, {
'$pull': {
'scores': {
'score': coll.minitem
}
}
})
});
});