Unwind embedded document in mongodb - mongodb

I have documents like below.
{
"_id": {
"$oid": "526fdc1fd6b0a8182300009c"
},
"body": "test abc",
"emb" : [{"body":"text","em":"abc.com","auth":"XYZ"},
{"body":"text","em":"abc.com","auth":"ABC"}
]
}
{
"_id": {
"$oid": "526fdc1fd6b0a8182300009d"
},
"body": "test abc",
"emb" : [{"body":"text","em":"abc.com","auth":"PQR"},
{"body":"text","em":"abc.com","auth":"ABC"}
]
}
If I want to count occurrences of each "auth" in the inner array of documents, how can I do that? The result I am expecting is
"ABC":2
"PQR":1
"XYZ":1

$unwind the emb array with {$unwind: "$emb"}
group by emb.auth while counting with {$group: { _id: "$emb.auth", count: { $sum:1 } } }
This gives you the information you want, although in a slightly different syntax:
{ _id:"ABC", count:2 },
{ _id:"PQR", count:1 },
{ _id:"XYZ", count:1 }

Related

MongoDB - Move some fields from the array into another array

I have this simplified MongoDB document and would like to change something because there is quite a lot of redundant data. This field "activeUsersLookup" is the result of aggregation which returns data I'd like to put inside the first users array.
First id:
"_id": "80b1565a-faf4-4e68-9bd6-8344060e8d3a" matches
id from activeUsersLookup the same story is with user IDs.
[{
"_id": "80b1565a-faf4-4e68-9bd6-8344060e8d3a",
"users": [
{
"_id": "eaa946da-2708-443e-ab4c-b6db357050ca",
"lastactive": {
"$date": {
"$numberLong": "1637922656000"
}
}
},
{
"_id": "4972ba13-6f4e-4943-be07-15802e22e0dd",
"lastactive": {
"$date": {
"$numberLong": "1653286066000"
}
}
},
{
"_id": "6c4a62ce-c6c6-430f-a0cd-d348ec77dbb2",
"lastactive": {
"$date": {
"$numberLong": "1558623982000"
}
}
}
],
"activeUsersLookup": [
{
"_id": "80b1565a-faf4-4e68-9bd6-8344060e8d3a",
"users": [
{
"_id": "eaa946da-2708-443e-ab4c-b6db357050ca",
"activities": 2
},
{
"_id": "6c4a62ce-c6c6-430f-a0cd-d348ec77dbb2",
"activities": 1
}
],
"sumOfActivities": 3
}
]
}]
So more or less the final document should look like this:
[{
"_id": "80b1565a-faf4-4e68-9bd6-8344060e8d3a",
"users": [
{
"_id": "eaa946da-2708-443e-ab4c-b6db357050ca",
"lastactive": {
"$date": {
"$numberLong": "1637922656000"
}
},
"activities": 2
},
{
"_id": "4972ba13-6f4e-4943-be07-15802e22e0dd",
"lastactive": {
"$date": {
"$numberLong": "1653286066000"
}
},
"activities": 0
},
{
"_id": "6c4a62ce-c6c6-430f-a0cd-d348ec77dbb2",
"lastactive": {
"$date": {
"$numberLong": "1558623982000"
}
},
"activities": 1
},
"sumOfActivities": 3
]
}]
I've tried with:
{
$addFields: {
'licenses.activities': '$activeUsersLookup.users.activities'
}
}
But this gives me an empty array so I must be doing something wrong.
The next stage would be to sum all those activities as sumOfActivities and the last stage would be unset activeUsersLookup.
What magic tricks must I do to have the needed result? :)
I don't think the expected result you posted for the "sumOfActivities": 3 in the users array is valid.
Assume that you are trying to achieve the result as below:
[{
"_id": "80b1565a-faf4-4e68-9bd6-8344060e8d3a",
"users": [...],
"sumOfActivities": 3
}]
The query is a bit long:
$set - Set activeUsersLookup field as object.
1.1. $first - Get the first document from 1.2.
1.2. $filter - Filter document(s) from activeUsersLookup by matching _id for the document in activeUsersLookup with _id (root document).
$set
2.1. - Set users array.
2.1.1. $map - Iterate the documents in users array and return a new array.
2.1.2. $mergeObjects - Merge current documents with the documents with activities field.
2.1.3. $ifNull - Set activities as 0 if no result returned from 2.1.4.
2.1.4. $getField - Get the activities field from the result 2.1.5.
2.1.5. $first - Get the first document from the result 2.1.6.
2.1.6. $filter - Filter the activeUsersLookup.users documents by matching _id for the document (users array) with _id for the current document.
2.2. Set sumOfActivities field.
$unset - Remove activeUsersLookup field.
db.collection.aggregate([
{
$set: {
activeUsersLookup: {
$first: {
$filter: {
input: "$activeUsersLookup",
cond: {
$eq: [
"$$this._id",
"$_id"
]
}
}
}
}
}
},
{
$set: {
users: {
$map: {
input: "$users",
as: "user",
in: {
$mergeObjects: [
"$$user",
{
activities: {
"$ifNull": [
{
"$getField": {
"field": "activities",
"input": {
$first: {
$filter: {
input: "$activeUsersLookup.users",
cond: {
$eq: [
"$$this._id",
"$$user._id"
]
}
}
}
}
}
},
0
]
}
}
]
}
}
},
sumOfActivities: "$activeUsersLookup.sumOfActivities"
}
},
{
$unset: "activeUsersLookup"
}
])
Sample Mongo Playground

MongoDB Aggregate Query to find the documents with missing values

I am having a huge collection of objects where the data is stored for different employees.
{
"employee": "Joe",
"areAllAttributesMatched": false,
"characteristics": [
{
"step": "A",
"name": "house",
"score": "1"
},
{
"step": "B",
"name": "car"
},
{
"step": "C",
"name": "job",
"score": "3"
}
]
}
There are cases where the score for an object is completely missing and I want to find out all these details from the database.
In order to do this, I have written the following query, but seems I am going wrong somewhere due to which it is not displaying the output.
I want the data in the following format for this query, so that it is easy to find out which employee is missing the score for which step and which name.
db.collection.aggregate([
{
"$unwind": "$characteristics"
},
{
"$match": {
"characteristics.score": {
"$exists": false
}
}
},
{
"$project": {
"employee": 1,
"name": "$characteristics.name",
"step": "$characteristics.step",
_id: 0
}
}
])
You need to use $exists to check the existence
playground
You can use $ifNull to handle both cases of 1. the score field is missing 2. score is null.
db.collection.aggregate([
{
"$unwind": "$characteristics"
},
{
"$match": {
$expr: {
$eq: [
{
"$ifNull": [
"$characteristics.score",
null
]
},
null
]
}
}
},
{
"$group": {
_id: null,
documents: {
$push: {
"employee": "$employee",
"name": "$characteristics.name",
"step": "$characteristics.step",
}
}
}
},
{
$project: {
_id: false
}
}
])
Here is the Mongo playground for your reference.

mongodb - find previous and next document in aggregation framework

After applying a long pipeline to my collection I can obtain something like this:
{
{
"_id": "main1",
"title": "First",
"code": "C1",
"subDoc": {
"active": true,
"sub_id": "main1sub1",
"order": 1
}
},
{
"_id": "main2",
"title": "Second",
"code": "C2",
"subDoc": {
"active": true,
"sub_id": "main2sub1",
"order": 1
}
},
{
"_id": "main3",
"title": "Third",
"code": "C3",
"subDoc": {
"active": false,
"sub_id": "main3sub1",
"order": 1
}
}
}
The documents are already in the correct order. Now I have to find the document immediately preceding or following the one corresponding to a given parameter. For example, if I know { "code" : "C2" } I have to retrieve the previous document (example document with "code" : "C1").
I only need to get that document, not the others.
I know how to do it using the find () method and applying sort () and limit () in sequence, but I want to get the document directly in the aggregation pipeline, adding the necessary stages to do it.
I've tried some combinations of $ indexOfArray and $ arrayElemAt, but the first problem I encounter is that I don't have an array, it's just documents.
The second problem is that the parameter I know might sometimes be inside the subdocument, for example {"sub_id": "main3sub1"}, and again I should always get the previous or next parent document as a response (in the example, the pipeline should return document "main2" as previous document)
I inserted the collection in mongoplayground to be able to perform the tests quickly:
mongoplayground
Any idea?
If you want to retrieve only the previous document, use the following query:
First Approach:
Using $match,$sort,$limit
db.collection.aggregate([
{
$match: {
code: {
"$lt": "C2"
}
}
},
{
"$sort": {
code: -1
}
},
{
$limit: 1
}
])
MongoDB Playground
Second Approach:
As specified by # Wernfried Domscheit,
Converting to array and then using $arrayElemAt
db.collection.aggregate([
{
$group: {
_id: null,
data: {
$push: "$$ROOT"
}
}
},
{
$addFields: {
"index": {
$subtract: [
{
$indexOfArray: [
"$data.code",
"C2"
]
},
1
]
}
}
},
{
$project: {
_id: 0,
data: {
$arrayElemAt: [
"$data",
"$index"
]
}
}
},
{
$replaceRoot: {
newRoot: "$data"
}
}
])
MongoDB Playground

count based on nested key mongodb

How can i count based on xTag key is on doc
I tried this but it does not provide me actual count
db.collection.find({
"products.xTag": {
$exists: false
}
}).count();
when you run with $exist:true i would expect result 1
When you run with $exist:false i would expect result 3
Playground: https://mongoplayground.net/p/_gf7RzGc8oB
Structure:
[
{
"item": 1,
"products": [
{
"name": "xyz",
"xTag": 32423
},
{
"name": "abc"
}
]
},
{
"item": 2,
"products": [
{
"name": "bob",
},
{
"name": "foo"
}
]
}
]
It is not possible with find(), You can use aggregate(),
$unwind deconstruct products array
$match your condition
$count total documents
db.collection.aggregate([
{ $unwind: "$products" },
{ $match: { "products.xTag": { $exists: false } } },
{ $count: "count" }
])
Playground

find list of users based on multiple condition using aggregation

I want to know how to use aggregation to perform this.
find userId list who perform add to cart and item view event more then 2 times
Collection name event.
{
_id:1,
name:"Add To Cart",
userId:1
}
{
_id:2,
name:"Searched",
userId:2
}
{
_id:3,
name:"Add To Cart",
userId:1
}
{
_id:4,
name:"Item View",
userId:1
}
{
_id:5,
name:"Add To Cart",
userId:2
}
{
_id:6,
name:"Item View",
userId:1
}
my query-
db.getCollection("event").aggregate([ {$match:{$or:[{"name":"Add to
Cart"},{"name":"Item Viewed"}]}},
{$group:{_id: {userId:"$userId",name:"$name"},count:{$sum:1}}},
{$match:{count:{$gte:2}}},
{$group: {_id:"$_id.userId",event:{$push:"$_id.name"}}} ,
{$project:{size:{$size:"$event"}}},
{$match:{size:{$gte:2}}}
]).pretty();
output expected -from above collection
{userId:1}
I want only those userId who perform Add to cart and Item view event(both) more then 2 times.
Here is Full Query ,
db.Test.aggregate(
// Pipeline
[
{
$group: {
"_id": {
"userId": "$userId",
"name": "$name"
},
"count": {
"$sum": 1
}
}
},
{
$match: {
"count": {
"$gte": 2
}
}
},
{
$group: {
"_id": "$_id.userId",
"event": {
"$push": "$_id.name"
}
}
},
{
$project: {
"size": {
"$size": "$event"
}
}
},
{
$match: {
"size": {
"$gte": 2
}
}
},
]
);
You will get your userId in _id field.