Check for value to be in an array in $cond - mongodb

Im trying to project a value in my aggregation pipeline based on if any array in an array contaibns a specific value.
This is a simplified version of how the data looks:
[
{
"permissions": [
{
"owners": [
"1"
]
}
]
},
{
"permissions": [
{
"owners": [
"2",
"3"
]
}
]
}
]
And Ive tried to do the following (with "2" being the example value I am searching for):
{
"$project": {
owner: {
$cond: {
if: {
$in: [
"2",
"$permissions.owners"
]
},
then: true,
else: false
}
}
}
}
Playground Example
But it always ends up being false. Any ideas?

The problem is $permissions.owners is a nested array. Check this example to look that.
So you have to $unwind the array. But, if there is only one array you can look for into first position in the array like this
But, assuming there could be many arrays, you can use this query.
$unwind the array to get each value separated.
$set the value into owner variable if exists the number 2.
$group to get values again.
At this point, exists a variable called owner which is a boolean array. So the next step is similar to the query you have.
$set again to know if exists true in any value, so, the variable will be true too.
db.collection.aggregate([
{
"$unwind": "$permissions"
},
{
"$set": {
"owner": {
"$in": [
"2",
"$permissions.owners"
]
}
}
},
{
"$group": {
"_id": "$_id",
"permissions": {
"$push": "$permissions"
},
"owner": {
"$push": "$owner"
}
}
},
{
"$set": {
"owner": {
"$in": [
true,
"$owner"
]
}
}
}
])
Example here

Related

How to query an array of nested data in MongoDB

This is my collection in my db:
{
"items": [
{
"id": "1",
"audit": [
{
"validFrom": ISODate("2021-01-20T14:24:57.483Z"),
"validTo": ISODate("2024-01-20T14:24:57.483Z")
}
]
},
{
"id": "1",
"audit": [
{
"validFrom": ISODate("2021-01-19T14:24:57.483Z"),
"validTo": ISODate("2024-01-19T14:24:57.483Z")
}
]
}
]
}
Part 1:
I wanted to query validFrom. And while querying, I want to display that specific audit element alone. I tried these queries:
This query returned only the first element that matched the condition
db.Balances.find({"items.audit.validto":{"$lte": ISODate("2024-01-20T14:24:57.483Z")}},{"items.$":1})
This query returned all data of that collection alone irrespective of the filter
db.Balances.find({""items.audit.validto":{"$lte": ISODate("2024-01-20T14:24:57.483Z")}},{"items":1})
Part 2:
After getting the desired result, I want to display the audit list alone instead of the entire item list
Expected Output:
"audit": [
{
"validFrom": ISODate("2021-01-20T14:24:57.483Z"),
"validTo": ISODate("2024-01-20T14:24:57.483Z")
}
]
This is one way of doing it using an aggregation pipeline.
Unwind the items array.
Filter out the elements matching the criteria.
Filter out the elements in the audit array matching the criteria.
db.collection.aggregate([
{
"$unwind": "$items"
},
{
"$match": {
"items.audit.validTo": {
"$lte": ISODate("2024-01-20T14:24:57.483Z")
}
}
},
{
"$project": {
"audit": {
"$filter": {
"input": "$items.audit",
"as": "elem",
"cond": {
"$lte": [
"$$elem.validTo",
ISODate("2024-01-20T14:24:57.483Z")
]
}
}
},
_id: 0
}
}
])
Playground link.

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

MongoDB Aggregation: How to check if an object containing multiple properties exists in an array

I have an array of objects and I want to check if there is an object that matches multiple properties. I have tried using $in and $and but it does not work the way I want it to.
Here is my current implementation.
I have an array like
"choices": [
{
"name": "choiceA",
"id": 0,
"l": "k"
},
{
"name": "choiceB",
"id": 1,
"l": "j"
},
{
"name": "choiceC",
"id": 2,
"l": "l"
}
]
I am trying to write aggregation code that can check if there is an object that contains both "id":2 and "l":"j" properties. My current implementation checks if there is an object containing the first property then checks if there is an object containing the second one.
How can I get my desired results?
Below, see my aggregation query. The full code is here
db.poll.aggregate([
{
"$match": {
"_id": 100
}
},
{
$project: {
numberOfVotes: {
$and: [
{
$in: [
2,
"$choices.id"
]
},
{
$in: [
"j",
"$choices.l"
]
}
]
},
}
}
])
The above query returns true yet there is no object in the array both of the properties id:2 and "l":"J". I know the code works as expected. How can I get my desired results?
You want to use something like $elemMatch
db.collection.find({
choices: {
$elemMatch: {
id: 2,
l: "j"
}
}
})
MongoPlayground
EDIT
In an aggregation $project stage I would use $filter
db.poll.aggregate([
{
"$match": {
"_id": 100
}
},
{
$project: {
numberOfVotes: {
$gt: [
{
$size: {
$filter: {
input: "$choices",
as: "choice",
cond: {
$and: [
{
$eq: [
"$$choice.id",
2
]
},
{
$eq: [
"$$choice.l",
"j"
]
}
]
}
}
}
},
0
]
}
}
}
])
MongoPlayground