Aggregation: group date in nested documents (nested object) - mongodb

I've a mongo database with user information. If new userdata is added I do a duplicate check and in case of a duplicate entry, I do not create a new document, but instead update the existing one with a nested node (under tracking) adding the timestamp and some other informations.
{
"_id": "5e95dee277dcc55e9d18bf1a",
"email": "test#test.com",
"tracking": [
{
"domain": "mydomain",
"subdomain": "",
"ip": "59.214.120.68",
"timestamp": "2020-03-21 20:06:12",
"externalID": "82"
},
{
"domain": "mydomain",
"subdomain": "",
"ip": "99.214.130.33",
"timestamp": "2020-03-26 18:43:01",
"externalID": "483"
},
{
"domain": "mydomain",
"subdomain": "",
"ip": "19.214.131.22",
"timestamp": "2020-03-26 18:48:42",
"externalID": "485"
}
]
}
Now I'm trying to aggregate the documents and group/count them by date. Is there any option how I can do this with diffrent number of nodes under tracking for each document?

You can do something like this.
{
$unwind: {
path: "$tracking",
preserveNullAndEmptyArrays: true
}
}, {
$group: {
_id: "$tracking.timestamp",
count: {
$sum: 1
}
}
}

It would be much better if you could store datetimes as actual datetime type and not string representations. But assuming you cannot for now, to group by the date component without the time (which is what I believe you seek) you can use substr:
db.foo.aggregate([
{$unwind: "$tracking"}
,{$group: {"_id": {$substr: [ "$tracking.timestamp", 0, 10] } , n: {$sum:1} }}
]);

Related

MongoDB use array field's element to $set a new field of the document

In the database, I have documents like the following
Ticket {
"eventHistory": [
{
"event": "CREATED",
"timestamp": "aa-bb-cccc"
},
{
"event": "ASSIGNED",
"timestamp": "ii-jj-kkkk"
},
...
{
"event": "CLOSED",
"timestamp": "xx-yy-zzzz"
}
]
}
I would like to add a closedAt field to the relevant Tickets, getting the value from the eventHistory array's last element. The resultant document would look like the following
Ticket {
"eventHistory": [
{
"event": "CREATED",
"timestamp": "aa-bb-cccc"
},
{
"event": "ASSIGNED",
"timestamp": "ii-jj-kkkk"
},
...
{
"event": "CLOSED",
"timestamp": "xx-yy-zzzz"
}
],
"closedAt": "xx-yy-zzzz"
}
The following pipeline allows me to use the entire object that's present as the eventHistory array's last element.
db.collection.updateMany(
<query>,
[
"$set": {
"closedAt": {
"$arrayElemAt": [
"$eventHistory",
-1
]
}
}
]
...
)
But I want to use only the timestamp field; not the entire object.
Please help me adjust (and/or improve) the pipeline.
One option to fix your query is:
db.collection.updateMany(
<query>,
[
{
$set: {
"Ticket.closedAt": {
$last: "$Ticket.eventHistory.timestamp"
}
}
}
])
See how it works on the playground example
But note that you assume that last item is a closing one. Is this necessarily the case? Otherwise you can validate it.

Update recursive document in MongoDB

I have a recursive structure in one of my MongoDB collections, like so:
{
"id": String,
"date": Date,
"text": String
"replies": [{
// Same structure as above
}]
}
I need to update a document in this hierarchy by adding in a reply to a nested document. The guarantees are as follows:
The object to reply to is guaranteed to exist.
The path to the object to which a reply is to be posted is known (i.e., we have a sequence of the _id attributes to navigate).
The recursion depth is not bounded.
Searching SO gives me the following relevant questions:
Querying, 2013 - The recommendation was to switch to a graph DB.
Updating, 2015 - The recommendation was to use the $[] operator.
Based on the latter, my attempt was:
await commCollection.update(
{ _id: topLevel['_id'] },
{
$push: {
'replies.$[].$[comment_arr].replies': {
id: commentId,
text: comment,
date,
replies: []
}
}
},
{ arrayFilters: [{ 'comment_arr.id': responseTo }]}
);
where topLevel is the root document, responseTo is the id attribute of the object to add the reply to. This however, does not seem to work. What am I doing wrong, and how do I achieve this?
Update: An example below. Here's an example of a document from MongoDB Atlas:
{
"_id": {
"$oid": "605fdb8d933c5f50b4d2225e"
},
"id": "mr9pwc",
"username": "recrimination-logistical",
"upvotes": {
"$numberInt": "0"
},
"downvotes": {
"$numberInt": "0"
},
"text": "Top-level comment",
"date": {
"$date": {
"$numberLong": "1616894861300"
}
},
"replies": [{
"id": "dflu1h",
"username": "patrolman-hurt",
"upvotes": {
"$numberInt": "0"
},
"downvotes": {
"$numberInt": "0"
},
"text": "Testing reply level 1!",
"date": {
"$date": {
"$numberLong": "1618387567042"
}
},
"replies": [] // ----> want to add a reply here
}]
}
I've indicated where we want a reply added. responseTo in this case would be dflu1h.
Just need to remove $[] positional
Field name in arrayFilters must be an alphanumeric string beginning with a lowercase letter, Change comment_arr to any alphabets only like commentArr, remove special characters
await commCollection.update(
{ _id: topLevel['_id'] },
{
$push: {
"replies.$[commentArr].replies": {
id: commentId,
text: comment,
date,
replies: []
}
}
},
{ arrayFilters: [{ "commentArr.id": responseTo }] }
)
Playground

How to use aggregation to pull information from a nested array on objects in mongoDB

I am trying to get the hang of the aggregation in mongoDB, I am new at this and I think I kinda got lost.
I have a collection of users, each user has an array of social networks, and each item in this array is an object.
How do I get value only from a given social network/s object/s.
{
"user":{
"_id": "d10430c8-e59",
"username": "John",
"password": "f7wei93",
"location": "UK",
"gender": "Male",
"age": 26,
"socials": [
{
"type": "instagram",
"maleFollowers": 23000,
"femaleFollowers": 65000,
"posts": 5400,
"avgFollowerAge": 22
},
{
"type": "facebook",
"maleFollowers": 4000,
"femaleFollowers": 6700,
"posts": 330,
"avgFollowerAge": 25
},
{
"type": "snapchat",
"maleFollowers": 873,
"femaleFollowers": 1200,
"posts": 1200,
"avgFollowerAge": 21
},
]
}
}
I want to get the totalFollowersCount ( maleFollowers + femaleFollowers)
for each social network type that is given in an array.
for example ["instagram", "snapchat"] will only give me the totalFollowersCount for this two specific networks.
I started playing around with aggregation but didn't figure it out all the way.
First, we need to flatten the socials array. Then, we group by socials type + 'user' fields and sum( maleFollowers + femaleFollowers).
With $match operator, we filter desired social networks.
db.users.aggregate([
{
$unwind: "$user.socials"
},
{
$group: {
_id: {
user: "$user._id",
type: "$user.socials.type"
},
"totalFollowersCount": {
$sum: {
$add: [
"$user.socials.femaleFollowers",
"$user.socials.maleFollowers"
]
}
}
}
},
{
$match: {
"_id.type": {
$in: [
"instagram",
"snapchat"
]
}
}
}
])
MongoPlayground

MongoDB aggregation on same collection

I need to get the value from another document and here are my sample documents :
[
{
"product_code": "0172745",
"condition_type": "ZPRE",
"price_group": "33",
"visibility": "brp, network",
"sales_org": "1010",
"distr_channel": "10",
"price_type": "dealer",
"price_market": "na",
"valid_from": "2019-07-01",
"valid_to": "9999-12-31",
"price_price_uom": "5.30",
"price_uom": "PC",
"price_sales_uom": "12.90",
"sales_uom": "PC",
"currency": "CAD",
"last_change_date": "2020-01-17T20:14:15"
}
},
{
"product_code": "0172745",
"condition_type": "ZPBA",
"price_group": "2R",
"visibility": "brp, network",
"sales_org": "1010",
"distr_channel": "10",
"price_type": "dealer",
"price_market": "na",
"valid_from": "2019-07-01",
"valid_to": "9999-12-31",
"price_price_uom": "5.30",
"price_uom": "PC",
"price_sales_uom": "5.300",
"sales_uom": "PC",
"currency": "CAD",
"last_change_date": "2020-01-17T20:14:15"
}
]
Output should be document 2 + price_sales_uom from document 1, aggregation is product_code and condition_type:
{
"product_code": "0172745",
"condition_type": "ZPBA",
"price_group": "2R",
"price_sales_uom": "12.90"
}
Please try below query, this will group all documents based on product_code :
db.collection.aggregate([
{
$group: {
_id: '$product_code', product_code: { $first: '$product_code' },
condition_type: { $last: '$condition_type' },
price_group: { $last: '$price_group' },
price_sales_uom: { $max: '$price_sales_uom' }
}
}, { $project: { _id: 0 } }])
Note : At it's given state of this question, this query assumes to get maximum value for price_sales_uom over matched documents (If it has to be from first document use $first), Similarly condition_type, price_group are being retrieved from last document in the group, but for product_code really doesn't matter whether $first or $last as they'll are same.

mongodb aggregation - nested group

I'm trying to perform nested group, I have an array of documents that has two keys (invoiceIndex, proceduresIndex) I need the documents to be arranged like so
invoices (parent) -> procedures (children)
invoices: [ // Array of invoices
{
.....
"procedures": [{}, ...] // Array of procedures
}
]
Here is a sample document
{
"charges": 226.09000000000003,
"currentBalance": 226.09000000000003,
"insPortion": "",
"currentInsPortion": "",
"claim": "notSent",
"status": "unpaid",
"procedures": {
"providerId": "9vfpjSraHzQFNTtN7",
"procedure": "21111",
"description": "One surface",
"category": "basicRestoration",
"surface": [
"m"
],
"providerName": "B Dentist",
"proceduresIndex": "0"
},
"patientId": "mE5vKveFArqFHhKmE",
"patientName": "Silvia Waterman",
"invoiceIndex": "0",
"proceduresIndex": "0"
}
Here is what I have tried
https://mongoplayground.net/p/AEBGmA32n8P
Can you try the following;
db.collection.aggregate([
{
$group: {
_id: "$invoiceIndex",
procedures: {
$push: "$procedures"
},
invoice: {
$first: "$$ROOT"
}
}
},
{
$addFields: {
"invoice.procedures": "$procedures"
}
},
{
"$replaceRoot": {
"newRoot": "$invoice"
}
}
])
I retain the invoice fields with invoice: { $first: "$$ROOT" }, also keep procedures's $push logic as a separate field. Then with $addFields I move that array of procedures into the new invoice object. Then replace root to that.
You shouldn't use the procedureIndex as a part of _id in $group, for you won't be able to get a set of procedures, per invoiceIndex then. With my $group logic it works pretty well as you see.
Link to mongoplayground