Related
I have this query:
collection.aggregate([
{ $match:{ "card.discovery": true } }
{ $sample: { size: 4 } }
]);
And it works because I need to get 4 random entries from that collection, but card.discovery can be true or null, and this obviously only returning documents with card.discovery set to true.
How can I filter the documents so that card.discovery can be or true or null?
You can take advantage of the $in operator:
{ $match: { "card.discovery": { $in: [ true, null ] } } }
Mongo Playground
Try this $or option
collection
[{
"_id" : 1.0,
"card" : {
"discovery" : true
}
}
{
"_id" : 2.0,
"card" : {
"discovery" : null
}
}
{
"_id" : 3.0,
"card" : {
"discovery" : true
}
}
{
"_id" : 4.0,
"card" : {
"discovery" : true
}
}
{
"_id" : 5.0,
"card" : {
"discovery" : null
}
}
{
"_id" : 6.0,
"card" : {
"discovery" : false
}
}
{
"_id" : 7.0,
"card" : {
"discovery" : false
}
}]
Query
db1.aggregate([
{ $match: { $or: [{ "card.discovery": true }, { "card.discovery": null }] }, },
{ $sample: { size: 4 } }
]).toArray();
Output
[
{
"_id": 4,
"card": {
"discovery": true
}
},
{
"_id": 1,
"card": {
"discovery": true
}
},
{
"_id": 3,
"card": {
"discovery": true
}
},
{
"_id": 5,
"card": {
"discovery": null
}
}
]
}]
You can use the $in operator:
collection.aggregate([
{ $match: { "card.discovery": { $in: [null, true] } } },
{ $sample: { size: 4 } }
]);
Alternatively, you can use the $or operator:
collection.aggregate([
{ $match: { $or: ["{card.discovery": true }, {"card.discovery": null}] } },
{ $sample: { size: 4 } }
]);
My MongoDB stores documents having the following structure:
{
"application_detail":{},
"curl_detail":{
"Curl1":{
"key1":"value1",
"key2":"value2"
},
"Curl2":{
"key1":"value1",
"key2":"value2"
},
"Curl3":{
"key1":"value1",
"key2":"value2"
},
"Curl4":{
"key1":"value1",
"key2":"value2"
},
/*total number of curls are unknown*/
}
}
How can I fetch key1 for all the curls present under curl_detail from mongoDB using express and mongoose?
Expected Output:
{
"curl_detail": {
"Curl1": {
"key1": "value1"
},
"Curl2": {
"key1": "value1"
},
"Curl3": {
"key1": "value1"
},
"Curl4": {
"key1": "value1"
}
}
}
NOTE: Total number of curls are unknown.
I would like to propose you changing data structrure from
"curl_detail": {
"Curl1": {
"key1": "value1",
"key2": "value2"
},
"Curl2": {
"key1": "value1",
"key2": "value2"
}
}
to
"curl_detail": [{
"key1": "value1",
"key2": "value2"
},
{
"key1": "value1",
"key2": "value2"
}
]
Storing data structure in array will allow you to find some data in an easier way. Also there's no need to have object with properties like Curl1, Curl2 etc.
For the new data structure there are already some answers with the explanation:
1) Mongo find value with unknown parent key
2) Query MongoDB by value when parent key is unknown
3) MongoDB: Find document given field values in an object with an unknown key
UPDATE
If there's no possibility to change the data structure please see this solution (trick with use of $arrayToObject / $objectToArray to determine the name of nested key e.g. "Curl1"):
db.collection.aggregate([{
$addFields: {
curl_detail: {
$arrayToObject: {
$map: {
input: {
$objectToArray: "$curl_detail"
},
as: "details",
in: {
k: "$$details.k",
v: {
key1: "$$details.v.key1"
}
}
}
}
}
}
},
{
$project: {
_id: 0,
curl_detail: 1
}
}
])
This outputs the following:
[
{
"curl_detail": {
"Curl1": {
"key1": "value1"
},
"Curl2": {
"key1": "value1"
},
"Curl3": {
"key1": "value1"
},
"Curl4": {
"key1": "value1"
}
}
}
]
You can check it through Mongo Playground
You can use projection for the query you are looking for:
collection.find({}, {"curl_detail":1}, (findError, results) => {
if(findError) {
//handle the error
return;
}
// here is the expected result
console.log();
});
If this result also works for you:
[
{
"curl_detail": {
"Curl1": "value1",
"Curl2": "value1",
"Curl3": "value1",
"Curl4": "value1"
}
}
]
You can use the following aggregation using the $objectToArray and $arrayToObject
db.collection.aggregate([
{
$addFields: {
curl_detail: {
$map: {
"input": {
"$objectToArray": "$curl_detail"
},
"as": "el",
"in": {
"k": "$$el.k",
"v": "$$el.v.key1",
}
}
}
}
},
{
$project: {
_id: 0,
curl_detail: {
$arrayToObject: "$curl_detail"
}
}
}
])
Playground
You can use this aggregation with mongoose like this, let's say your model is MyModel:
MyModel.aggregate([
{
$addFields: {
curl_detail: {
$map: {
input: {
$objectToArray: '$curl_detail'
},
as: 'el',
in: {
k: '$$el.k',
v: '$$el.v.key1'
}
}
}
}
},
{
$project: {
_id: 0,
curl_detail: {
$arrayToObject: '$curl_detail'
}
}
}
]).then(res => {
console.log(res);
});
Below is my data structure.
{
"_id" : "room1",
"members" : [
{
"_id" : "member1",
"name" : "Michael",
"payments" : [
{
"month": "2018/09"
"amount": "20"
}
]
},
]
}
I want to push below object to Michael's payments
{
"month": "2018/09",
"amount": "5000"
}
In this case, What I want to is overwrite object, because month: "2018/09" already exist. Like below :
{
"_id" : "room1",
"members" : [
{
"_id" : "member1",
"name" : "Michale",
"payments" : [
{
"month": "2018/09"
"amount": "5000"
}
]
},
]
}
And, In case when I want to push object that not exist same month in payments, I want to add this object to payments.
{
"month": "2018/10",
"amount": "2000"
}
So the expected result is
{
"_id" : "room1",
"members" : [
{
"_id" : "member1",
"payments" : [
{
"month": "2018/09"
"amount": "5000"
},
{
"month": "2018/10"
"amount": "2000"
}
]
},
]
}
I tried like below, but it's not working. My code generate duplicated new month object every time I tried. How can I do this properly?
Rooms.update(
{
_id: "room1",
"members._id": "member1",
"members.$.payments": {
$not: {
$elemMatch: {
month: req.body.month
}
}
}
},
{
$addToSet: {
"members.$.payments": {
month: req.body.month,
amount: req.body.value
}
}
},
{ multi: true }, function (err, result) {
console.log(result)
}
)
You can use below command to add without duplicity either in months or amount
Rooms.update(
{
_id: "room1",
"members._id": "member1"
},
{
$addToSet: {
"members.$.payments": {
month: req.body.month,
amount: req.body.value
}
}
},function (err, result) {
console.log(result)
}
)
So I heard I have to determine duplication myself, so below is my code... it's writing now.,,
So Finally this is my code
Clubs.findOne({
uid: req.params.club_id,
"members._id": mongoose.Types.ObjectId(req.params.member_uid)
}, function(err, club){
let member = club.members.filter(el => {
if(el._id.equals(req.params.member_uid)) return el
})
let duplicated = false;
member[0].payments.map(el => {
if(el.month === req.body.month) duplicated = true
})
if(duplicated){
Clubs.update(
{
uid: req.params.club_id,
"members._id": mongoose.Types.ObjectId(req.params.member_uid),
},
{
$set: {
["members.$.payments."+index+".amount"] : req.body.value
}
},
function (err, result, third) {
if (err) throw err
console.log('result')
console.log(result)
res.json({})
}
)
} else {
Clubs.update(
{
uid: req.params.club_id,
"members._id": mongoose.Types.ObjectId(req.params.member_uid),
},
{
$push: {
"members.$.payments" : {
month : req.body.month,
amount: req.body.value
}
}
},
function (err, result, third) {
if (err) throw err
console.log('result')
console.log(result)
res.json({})
}
)
}
})
Perhaps consider changing the structure of your nested array to an object? So change this
{
"payments": [{
"month": "2018/09"
"amount": "5000"
},
{
"month": "2018/10"
"amount": "2000"
}
]
}
to this:
{
"payments": {
"2018/09": "5000",
"2018/10": "2000"
}
}
Then you can do a simple update:
Rooms.update({
_id: "room1",
"members._id": "member1",
"members.payments": {
$exists: true
}
}, {
$set: {
"members.payments." + req.body.month: req.body.value
}
},
)
I have the collection like below
{
"_id" : ObjectId("5b6538704ba0292b6c197770"),
"Name":"Name1",
"Status":"Good",
},
{
"_id" : ObjectId("5b6538704ba0292b6c197773"),
"Name":"Name2",
"Status":"Bad"
},
{
"_id" : ObjectId("5b6538704ba0292b6c197774"),
"Name":"Name3",
"Status":"Bad"
},{
"_id" : ObjectId("5b6538704ba0292b6c197774"),
"Name":"Name1",
"Status":"Bad"
},
{
"_id" : ObjectId("5b6538704ba0292b6c197775"),
"Name":"Name1",
"Status":"Good"
}
I have used the query to get the status wise count like below
db.Students.aggregate( [
{ $group: { _id: {
"Name": "$Name","Status":"$Status"}, StatusCount: { $sum: 1 } } }
, { "$project": { _id: 0, Name: "$_id.Name",Status : "$_id.Status", StatusCount:1 } }
] );
The result was
{
"Name":"Name1",
"StatusCount" : 2,
"Status" : "Good"
},
{
"Name":"Name2",
"StatusCount" : 1,
"Status" : "Bad"
}, {
"Name":"Name2",
"StatusCount" : 1,
"Status" : "Bad"
},
{
"Name":"Name1",
"StatusCount" : 1,
"Status" : "Bad"
}
The result what I am approaching is like
{
"Name":"Name1",
"Good" : 2,
"Bad" :1
},
{
"Name":"Name2",
"Good" : 0,
"Bad" :1
}
The result I am expecting to have the status of field names and count as its values. I have tried to do this but I could not make it happen. The status, for now, is only two like Good or Bad but may increase in real dataset.
By using the $arrayToObject operator and a final $replaceRoot pipeline step which has a $mergeObjects operator you will get your desired result.
You would need to run the following aggregate pipeline on MongoDB Server 3.4.4 or newer:
const pipeline = [
{ '$group': {
'_id': {
'Name': '$Name',
'Status': '$Status'
},
'StatusCount': { '$sum': 1 }
} },
{ '$group': {
'_id': '$_id.Name',
'counts': {
'$push': {
'k': '$_id.Status',
'v': '$StatusCount'
}
}
} },
{ '$replaceRoot': {
'newRoot': { '$mergeObjects': [
{ '$arrayToObject': '$counts' },
{ 'Name': '$_id' }
] }
} }
];
db.Students.aggregate(pipeline);
The docs are simple as:
[
{'id': '1', 'type': 'a', 'startedAt': '2017-06-11'},
{'id': '2', 'type': 'b', 'startedAt': ''},
{'id': '3', 'type': 'b', 'startedAt': '2017-06-11'}
]
And the expected aggregated result:
[
{'type': 'a', 'started': true, 'count': 1},
{'type': 'b', 'started': true, 'count': 1},
{'type': 'b', 'started': false, 'count': 1}
]
How to get above result with mongodb nodejs driver?
I've tried like below, but it didn't work ('started' was always null):
db.collection('docs').group(
{'type': '$type', 'started': {
$cond: [{$eq: ['$startedAt': '']}, false, true ]
}},
{},
{'total': 0},
'function(curr, result) {result.total++}'
)
You use .aggregate() here and not .group(), which is a different function altogether:
db.collection('docs').aggregate([
{ "$group": {
"_id": {
"type": "$type",
"started": {
"$gt": [ "$startedAt", "" ]
}
},
"count": { "$sum": 1 }
}}
],function(err, results) {
console.log(results);
})
The $gt operator returns true when the condition is met. In this case any content in a string is "greater than" an empty string.
If the field is actually "not present at all" then we can adapt with $ifNull. This gives a default have if the property does not actually exist, or otherwise evaluates to null.
db.collection('docs').aggregate([
{ "$group": {
"_id": {
"type": "$type",
"started": {
"$gt": [ { "$ifNull": [ "$startedAt", ""] }, "" ]
}
},
"count": { "$sum": 1 }
}}
],function(err, results) {
console.log(results);
})
This would produce:
{ "_id" : { "type" : "b", "started" : true }, "count" : 1 }
{ "_id" : { "type" : "b", "started" : false }, "count" : 1 }
{ "_id" : { "type" : "a", "started" : true }, "count" : 1 }
You can optionally $project afterwards to change the fields from being within _id in the results, but you really should not since this means an additional pass through results, when you can just as easily access the values anyway.
So just .map() on the result:
console.log(
results.map(function(r) {
return { type: r._id.type, started: r._id.started, count: r.count }
})
);
But with $project:
db.collection('docs').aggregate([
{ "$group": {
"_id": {
"type": "$type",
"started": {
"$gt": [ { "$ifNull": [ "$startedAt", ""] }, "" ]
}
},
"tcount": { "$sum": 1 }
}},
{ "$project": {
"_id": 0,
"type": "$_id.type",
"started": "$_id.started",
"count": "$tcount"
}}
],function(err, results) {
console.log(results);
})
Resulting in your desired format
{ "type" : "b", "started" : true, "count" : 1 }
{ "type" : "b", "started" : false, "count" : 1 }
{ "type" : "a", "started" : true, "count" : 1 }
For reference, the correct usage with .group() would be:
db.collection('docs').group(
function(doc) {
return {
"type": doc.type,
"started": (
(doc.hasOwnProperty('startedAt') ? doc.startedAt : "") > ""
)
}
},
[],
{ "count": 0 },
function(curr,result) {
result.count += 1
},
function(err,results) {
console.log(results);
}
);
Which returns:
[
{ "type" : "a", "started" : true, "count" : 1 },
{ "type" : "b", "started" : false, "count" : 1 },
{ "type" : "b", "started" : true, "count" : 1 }
]
But you really should no use that since .group() relies on JavaScript evaluation that runs much slower than what you can do with .aggregate()