How to duplicate fields in document in MongoDB? - mongodb

I am trying to duplicate some fields inside documents.
In the case we have fields in object, I want to duplicate them outside and vice versa.
My MongoDB version is 4.2.12.
Here is my collection :
{
'id': '1',
'object': {
'a': 'OK',
'b': ''
}
},
{
'id': '2',
'a': '',
'b': 'OK',
}
Here is what I want as a result :
{
'id': '1',
'a': 'OK',
'b': '',
'object': {
'a': 'OK',
'b': ''
}
},
{
'id': '2',
'a': '',
'b': 'OK',
'object': {
'a': '',
'b': 'OK'
}
}
Your help will be highly appreciated.

$addFields to add new fields
$cond with $ifNull to check if the field already exists or not
db.collection.aggregate([
{
"$addFields": {
"a": {
"$cond": {
"if": { "$ifNull": [ "$a", null ] },
"then": "$a",
"else": "$object.a"
}
},
"b": {
"$cond": {
"if": { "$ifNull": [ "$b", null ] },
"then": "$b",
"else": "$object.b"
}
},
"object": {
"$cond": {
"if": { "$ifNull": [ "$object", null ] },
"then": "$object",
"else": { a: "$a", b: "$b"}
}
}
}
}
])
Working example

Related

Update document where all items in an array has the same value

I am making a migration script where I need to update a status value on all documents where the items in an array has the same value
Data structure
[
{
_id: 'asdasd',
status: 'active',
approvers: [
{status: 'approved'},
{status: 'approved'}
]
},
{
_id: 'fghfgh',
status: 'active',
approvers: [
{status: 'approved'},
{status: 'awaiting_approval'},
]
}
]
So, in this case in want to update all documents to have status 'completed' where all approvers has status 'approved'
I haven't found a good way how to create a filter like this.
What I've currently tried to do is:
db.getCollection("assignmentRequest").aggregate([
{
$match: {
'approver.0.status': {$exists:true}
}
},
{
$project: {
_id: 0,
approver: 1,
status: 1,
noOfApprovers: { $cond: { if: { $isArray: "$approvers" }, then: { $size: "$approvers" }, else: 0}},
noOfApproversThatHasApproved: {
$size: {$filter: {
'input': '$approvers',
'as': 'approver',
'cond': {
'$and': [
{
$eq: ['$$approver.status', 'approved']
}
]
}
}}
},
},
},
{
$match: {$expr: { $eq: ["$noOfApprovers", "$noOfApproversThatHasApproved"] } }
},
{
$set: {'status': 'completed'}
},
{
$project: {_id:1, status:1 }
},
{
$merge: {into: 'assignmentRequests_copy', on: '_id', whenMatched: "replace" }
}])
The filter works, but I can't get the status to update. I'm sure there are plenty of things worng with my query, but I feel like I am going down the wrong path and that there must be a simpler way of achieving this. Any pointers or help would be highly appreciated.
Edit:
After the update I want the documents to look like this:
{
_id: 'asdasd',
status: 'completed',
approvers: [
{status: 'approved'},
{status: 'approved'}
]
},
{
_id: 'fghfgh',
status: 'active',
approvers: [
{status: 'approved'},
{status: 'awaiting_approval'},
]
}
]
Here's one way you could do it by using a pipeline in the update.
db.collection.update({
"approvers.status": "approved"
},
[
{
"$set": {
"status": {
"$cond": [
{
"$reduce": {
"input": "$approvers",
"initialValue": true,
"in": {
"$and": [
"$$value",
{"$eq": ["$$this.status", "approved"]}
]
}
}
},
"completed",
"$status"
]
}
}
}
],
{"multi": true}
)
Try it on mongoplayground.net.

mongoDB group by date and other column

i need some help grouping by date and by other column, at the moment i got:
[
{
'$project': {
'date': 1,
'source': 1,
'callDirection': 1,
'status': 1
}
}, {
'$match': {
'$or': [
{
'source': '501'
}, {
'source': '555'
}
]
}
}, {
'$group': {
'_id': 0,
'total': {
'$sum': 1
},
'answered': {
'$sum': {
'$cond': [
{
'$eq': [
'$status', 'ANSWERED'
]
}, 1, 0
]
}
},
'no answer': {
'$sum': {
'$cond': [
{
'$eq': [
'$status', 'NO ANSWER'
]
}, 1, 0
]
}
}
}
}
]
the result i got now is the totals:
_id:0
total:591
answered:443
no answer:129
what i need is to split the data by source and by date so i get the data return like this
date => 2022-01-23 , source => 501, answered => 12, noanswer => 2
date => 2022-01-23 , source => 555, answered => 5, noanswer => 5
date => 2022-01-24 , source => 501, answered => 6, noanswer => 3
date => 2022-01-24 , source => 555, answered => 22, noanswer => 6
example data:
"date": "2021-12-23 10:25:59","source": "501","callDirection": "Outgoing","status": "ANSWERED"
"date": "2021-12-23 11:21:19","source": "501","callDirection": "Outgoing","status": "NO ANSWER"
"date": "2021-12-24 01:21:19","source": "501","callDirection": "Outgoing","status": "ANSWERED"
"date": "2021-12-24 10:25:59","source": "555","callDirection": "Outgoing","status": "ANSWERED"
"date": "2021-12-25 12:55:19","source": "555","callDirection": "Outgoing","status": "ANSWERED"
im new to mongoDb and i need some help ,thanks a lot
Perhaps Something like this:
db.collection.aggregate([
{
$addFields: {
date: {
$substr: [
"$date",
0,
10
]
}
}
},
{
$group: {
_id: {
da: "$date",
so: "$source",
cd: "$callDirection"
},
answer: {
"$sum": {
"$cond": [
{
"$eq": [
"ANSWERED",
"$status"
]
},
1,
0
]
}
},
noanswer: {
"$sum": {
"$cond": [
{
"$eq": [
"ANSWERED",
"$status"
]
},
0,
1
]
}
}
}
},
{
$project: {
date: "$_id.da",
source: "$_id.so",
callDirection: "$_id.cd",
answer: 1,
noanswer: 1
}
}
])
Explained:
Replace the datetime string with date only string
Group by date,source & callDirection generating two new counting fields answer and noanswer from the status field.
Project only the necessary fields are needed
playground

MongoDB. Aggregate the sum of two arrays sizes

With MongoDB 3.4.10 and mongoose 4.13.6 I'm able to count sizes of two arrays on the User model:
User.aggregate()
.project({
'_id': 1,
'leftVotesCount': { '$size': '$leftVoted' },
'rightVotesCount': { '$size': '$rightVoted' }
})
where my Users are (per db.users.find())
{ "_id" : ObjectId("5a2b21e63023c6117085c240"), "rightVoted" : [ 2 ],
"leftVoted" : [ 1, 6 ] }
{ "_id" : ObjectId("5a2c0d68efde3416bc8b7020"), "rightVoted" : [ 2 ],
"leftVoted" : [ 1 ] }
Here I'm getting expected result:
[ { _id: '5a2b21e63023c6117085c240', leftVotesCount: 2, rightVotesCount: 1 },
{ _id: '5a2c0d68efde3416bc8b7020', leftVotesCount: 1, rightVotesCount: 1 } ]
Question. How can I get a cumulative value of leftVotesCount and rightVotesCount data? I tried folowing:
User.aggregate()
.project({
'_id': 1,
'leftVotesCount': { '$size': '$leftVoted' },
'rightVotesCount': { '$size': '$rightVoted' },
'votesCount': { '$add': [ '$leftVotesCount', '$rightVotesCount' ] },
'votesCount2': { '$sum': [ '$leftVotesCount', '$rightVotesCount' ] }
})
But votesCount is null and votesCount2 is 0 for both users. I'm expecting votesCount = 3 for User 1 and votesCount = 2 for User 2.
$leftVotesCount, $rightVotesCount become available only on the next stage. Try something like:
User.aggregate()
.project({
'_id': 1,
'leftVotesCount': { '$size': '$leftVoted' },
'rightVotesCount': { '$size': '$rightVoted' }
})
.project({
'_id': 1,
'leftVotesCount': 1,
'rightVotesCount': 1
'votesCount': { '$add': [ '$leftVotesCount', '$rightVotesCount' ] },
'votesCount2': { '$sum': [ '$leftVotesCount', '$rightVotesCount' ] }
})
You can't reference the project variables created in the same project stage.
You can wrap the variables in a $let expression.
User.aggregate().project({
"$let": {
"vars": {
"leftVotesCount": {
"$size": "$leftVoted"
},
"rightVotesCount": {
"$size": "$rightVoted"
}
},
"in": {
"votesCount": {
"$add": [
"$$leftVotesCount",
"$$rightVotesCount"
]
},
"leftVotesCount": "$$leftVotesCount",
"rightVotesCount": "$$rightVotesCount"
}
}
})
It turned out that $add supports nested expressions, so I was able to solve the issue by excluding intermediate variables:
User.aggregate().project({
'_id': 1,
'votesCount': { '$add': [ { '$size': '$leftVoted' }, { '$size': '$rightVoted' } ] }
});
// [ {_id: '...', votesCount: 3}, {_id: '...', votesCount: 2} ]

Mongo complex aggregate (condition based on group count)

Id need a mongo aggregate that given the sample data:
{
'employeeNumber': '1',
'companyId': '1',
'role': 'D',
'dateHired':ISODate("2013-11-26T00:00:00.0Z")
...
}
{
'employeeNumber': '1',
'companyId': '1',
'role': 'S',
'dateHired':ISODate("2013-11-26T00:00:00.0Z")
...
}
{
'employeeNumber': '1',
'companyId': '2',
'role': 'D',
'dateHired':ISODate("2013-11-26T00:00:00.0Z")
...
}
{
'employeeNumber': '2',
'companyId': '1',
'role': 'D',
'dateHired':ISODate("2013-11-26T00:00:00.0Z")
...
}
queries for a given companyId (e.g. companyId = 1, using match stage probably) and would return something like:
{
'employeeNumber': '1',
'companyId': '1',
'role': 'D','S'
'dateHired':ISODate("2013-11-26T00:00:00.0Z")
...
}
notice that
{
'employeeNumber': '1',
'companyId': '2',
'role': 'D'
'dateHired':ISODate("2013-11-26T00:00:00.0Z")
...
}
is not returned.
Ideally it would return the whole object as the collection has 10/12 fields.
By using aggregation you will not get exact expected output but you can get output like following:
{ "role" : [ "D" ], "employeeNumber" : "2" }
{ "role" : [ "S" ], "employeeNumber" : "3" }
{ "role" : [ "D", "S" ], "employeeNumber" : "1" }
And the query will be like:
db.collection.aggregate({
$group: {
_id: "$employeeNumber",
"role": {
"$push": "$role"
}
}
}, {
$project: {
"employeeNumber": "$_id",
"role": 1,
"_id": 0
}
})
Edit After question edit:
db.collection.aggregate({
$group: {
_id: {
employeeNumber: "$employeeNumber",
"companyId": "$companyId"
},
"role": {
"$push": "$role"
},
"dateHired": {
$last: "$dateHired"
}
}
}, {
$project: {
"employeesNumber": "$_id.employeeNumber",
"comapnyId": "$_id.companyId",
"role": 1,
"dateHired": 1,
"_id": 0
}
})

How to ORDER BY FIELD VALUE in MongoDB

In Mysql I often use the FIELD() function in the ORDER BY clause:
ORDER BY FIElD(id, '1', '6', '3', ...);
How does one get the same results in MongoDB? I tried the following:
.find(...).sort({id: [1, 6, 3]})
This did not work
We can use $indexOfArray
Console
db.collectionName.aggregate([{
$match: {
_id: {
$in: [249, 244]
}
}
}, {
$addFields: {
sort: {
$indexOfArray: [
[249, 244], "$_id"
]
}
}
},{
$sort: {
sort: 1
}
}])
PHP code
$data = $this->mongo->{$collectionName}->aggregate(
[
[
'$match' => ['_id' => ['$in' => $idList]]
],
[
'$addFields' => ['sort' => ['$indexOfArray' => [$idList, '$_id']]]
],
[
'$sort' => ['sort' => 1]
],
[
'$project' => [
'name' => 1
]
]
]
);
So for the record:
Given the array [1,6,3] what you want in your query is this:
db.collection.aggregate([
{ "$project": {
"weight": { "$cond": [
{ "$eq": ["_id": 1] },
3,
{ "$cond": [
{ "$eq": ["_id": 6] },
2,
{ "$cond": [
{ "$eq": ["_id": 3] },
1,
0
]},
]},
]}
}},
{ "$sort": { "weight": -1 } }
])
And that gives you specific "weights" by order of your "array" of inputs to "project" weights upon the results.