I have a ranks collection with a permissions field which are bitwise operators:
[
{
"_id": "xxxx",
"name": "Rank 1",
"permissions": 1
},
{
"_id": "xxxxxxxxx",
"name": "Rank 2",
"permissions": 2
}
]
Example users:
[
{
"_id":"1234",
"ranks":[
"xxxx",
"xxxxxxxxx"
]
}
]
The users collection containts a ranks value, which stores an array of the rank ids.
I'm wanting to get the user, and their ranks and set their permissions to a value.
const users = await this.collection.aggregate([
{
$match: { userID: '123' }
},
{ $limit: 1 },
{
$lookup: {
from: 'ranks',
localField: 'rank',
foreignField: '_id',
as: 'ranks'
}
},
{
$set: {
permissions: {
$arrayElemAt: ['$rank.permissions', 0]
}
}
},
{
$unwind: {
path: '$rank',
preserveNullAndEmptyArrays: true
}
}
]).toArray();
This obviously gets 1 value from the collection, I'm wanting to get all permissions and add the bitwise operators together.
Expected Output
{
"_id": "1234",
"ranks":[
"xxxx",
"xxxxxxxxx"
]
"permissions":3
}
Any help is appreciated!
Here's one way to "or" all the rank permissions by using a server-side javascript "$function".
db.users.aggregate([
{
"$match": {
"_id": 42
}
},
{
"$lookup": {
"from": "ranks",
"localField": "ranks",
"foreignField": "_id",
"pipeline": [
{
"$project": {
"_id": 0,
"permissions": 1
}
}
],
"as": "permissions"
}
},
{
"$set": {
"permissions": {
"$function": {
"body": "function(perms) {return perms.reduce((prevV, currV) => prevV | currV, 0)}",
"args": ["$permissions.permissions"],
"lang": "js"
}
}
}
}
])
Try it on mongoplayground.net.
With sample collection...
db = {
"permissions": [
{
"_id": "xxxx",
"name": "Rank 1",
"permissions": 1
},
{
"_id": "xxxxxxxxx",
"name": "Rank 2",
"permissions": 2
},
{
"_id": "xxxxxxx",
"name": "Rank 4",
"permissions": 4
}
],
"users": [
{
"_id": "1234",
"ranks": [
"xxxx",
"xxxxxxxxx"
]
},
{
"_id": "4567",
"ranks": [
"xxxx",
"xxxxxxx"
]
}
]
}
...try the following aggregation, which...
Finds the _id for the user 1234 in the users collection.
Looks for all the corresponding ranks in the permissions collection.
Unwinds to have one result per corresponding permission.
Aggregates the permissions and ranks.
db.users.aggregate([
{
$match: {
"_id": "1234"
}
},
{
$lookup: {
from: "permissions",
localField: "ranks",
foreignField: "_id",
as: "ranks"
}
},
{
$unwind: "$ranks"
},
{
$group: {
_id: "$_id",
ranks: {
$push: "$ranks._id"
},
permissions: {
$sum: "$ranks.permissions"
}
}
}
])
See MongoDB playground at...
https://mongoplayground.net/p/BCl57dNhupH
Important Note: This query groups the permissions by sum (rather than by boolean logical OR), so you must ensure that there are no duplicate permissions. If you can't ensure unique permissions per user, then suggest that the permissions are $pushed like the ranks, and then perform some post processing on the list of permissions to reduce via logical OR...
Related
I have two collections Posts an comments. I am storing comments with postID. I want to show comments count field when fetching all the posts data.
How do I achieve this?
// posts
{
postID: '123',
title: 'abc'
}
// comments
{
postID: '123',
comments: [
{
commentID: 'comment123',
comment: 'my Comment'
}
]
}
// Looking for this
{
postID: '123',
title: 'abc',
commentCount: 1
}
Here's one way you could do it.
db.posts.aggregate([
{
"$lookup": {
"from": "comments",
"localField": "postID",
"foreignField": "postID",
"pipeline": [
{
"$project": {
"_id": 0,
"commentCount": {"$size": "$comments"}
}
}
],
"as": "commentCount"
}
},
{
"$project": {
"_id": 0,
"postID": 1,
"title": 1,
"commentCount": {"$first": "$commentCount.commentCount"}
}
}
])
Try it on mongoplayground.net.
Try This.
pipeline = [{
"$lookup": {
"from": "comments",
"let": {
"postId": "$postId",
},
"pipeline": [
{
"$match": {
"$expr": {
"$eq": ["$postId", "$$postId"]
},
}
},
{
"$group": {
"_id": "$postId",
"comments_count": {"$sum": 1}
}
}
],
"as": "comments"
}
},
{
"$project": {
"_id": 0,
"postId": 1,
"title":1,
"comments_count": "$comments.comments_count"
}
}]
db.posts.aggregate(pipeline)
I've been struggling to get my around how to update a object in a nested array with a particular id. I've attempted to implement $set as shown below. I want to be able to update the task with an _id of 62ff74bfe80b11ade2d34455 with the data from the request body.
{
"_id": "62fa5aa25778ec97bc6ee231",
"user": "62f0eb5ebebd0f236abcaf9d",
"name": "Marketing Plan",
"columns": [
{
"name": "todo",
"_id": "62fa5aa25778ec97bc6ee233",
"tasks": [
{ ====> here
"title": "Task Four",
"description": "This is task four",
"subtasks": [
{
"name": "wash dshes",
"completed": false,
"_id": "62ff74bfe80b11ade2d34456"
},
{
"name": "do homework",
"completed": false,
"_id": "62ff74bfe80b11ade2d34457"
}
],
"_id": "62ff74bfe80b11ade2d34455"
}
]
},
{
"name": "doing",
"_id": "62fa5aa25778ec97bc6ee234",
"tasks": []
},
{
"name": "done",
"_id": "62fa5aa25778ec97bc6ee235",
"tasks": []
}
],
"__v": 0
}
const updatedTask = await Board.findOneAndUpdate(
{
"columns.tasks._id": req.params.id,
},
{ $set: { "columns.$.tasks": req.body } },
{ new: true }
);
You can use the positional operator in combination with an arrayfilter. Here's an example how you'd update a specific field of the relevant task:
db.collection.update({
"columns.tasks._id": req.params.id
},
{
"$set": {
"columns.$[].tasks.$[t].title": "it works"
}
},
{
"arrayFilters": [
{
"t._id": req.params.id
}
]
})
You can also try this on mongoplayground.
If you're looking for a way to replace the matching task object itself you can do:
db.collection.update({
"columns.tasks._id": req.params.id
},
{
"$set": {
"columns.$[].tasks.$[t]": req.body
}
},
{
"arrayFilters": [
{
"t._id": req.params.id
}
]
})
I have a mongodb collection as follows:
[
{
"_id": { "$oid": "609b8f06a28f6728d19b486d" },
"user1": "609952c2b112741634d27d89",
"user2": "609b8202b5a389099cae3ce6",
"messages": [
{
"body": "Hello user 2",
"user": "609952c2b112741634d27d89",
"readed": true,
"created": { "$date": "2021-05-13T01:38:07.502Z" }
},
{
"body": "How old are you?",
"user": "609952c2b112741634d27d89",
"readed": false,
"created": "2021-05-13T01:40:07.502Z"
},
{
"body": "I am fine. Are you ready?",
"user": "609b8202b5a389099cae3ce6",
"readed": false,
"created": "2021-05-13T01:42:07.502Z"
},
{
"body": "Yes. Lighgui start",
"user": "609952c2b112741634d27d89",
"readed": false,
"created": "2021-05-13T01:38:50.502Z"
}
]
}
]
I want to sort chats that have messages up first how do I do that?
also if possible i would like to get the latest message of each chat and the number of messages with readed=false
You can use
$unwind to destructure the array
$sort to sort the array based on created of messages
$group to restructure the array
Here is the code
db.collection.aggregate([
{
$unwind: {
path: "$messages",
preserveNullAndEmptyArrays: true
}
},
{
"$sort": {"messages.created": 1 }
},
{
"$group": {
"_id": "$_id",
user1: { $first: "$user1" },
user2: { $first: "$user2" },
"messages": { "$push": "$messages" }
}
}
])
Working Mongo playground
Hello i am trying to get my database to return both matched and empty results on a sub-document.
I am joining two tables using aggregate and lookup, below is the code
db.collection.aggregate([
{
$addFields: {
cut_off_date: { $toDate: "$shipment_cutoff_date" },
},
},
{
$lookup: {
from: "updates",
localField: "_id",
foreignField: "shipment_id",
as: "updates",
},
},
{
$match: {
"updates.description": { $ne: "All updates completed" },
},
},
]);
Challenge is i am trying to get All rows where all updates have been completed as well as all empty updates. If i remove the match parameters i get all the results including where the updates have been completed and i am trying to avoid doing a foreach after getting all my results.
Here is a snippet of the result without the match
{
"_id": "609927e31233700004370cfb",
"title": "Hello World",
"createdAt": "2021-05-10T12:32:35.799Z",
"updatedAt": "2021-05-10T15:58:59.149Z",
"updates": []
},
{
"_id": "60940ad73ced476b2d0b3626",
"createdAt": "2021-05-06T15:27:19.814Z",
"updatedAt": "2021-05-10T12:49:08.167Z",
"updates": [
{
"_id": "60952c0ed31c6283f302eb23",
"post_id": "60940ad73ced476b2d0b3626",
"description": "This is an update description",
"createdAt": "2021-05-07T12:01:18.815Z",
"updatedAt": "2021-05-07T12:01:18.815Z",
},
]
},
{
"_id": "60940ad73ced476b2d0b3626",
"createdAt": "2021-05-06T15:27:19.814Z",
"updatedAt": "2021-05-10T12:49:08.167Z",
"updates": [
{
"_id": "60952c0ed31c6283f302eb23",
"post_id": "60940ad73ced476b2d0b3626",
"description": "All updates completed",
"createdAt": "2021-05-07T12:01:18.815Z",
"updatedAt": "2021-05-07T12:01:18.815Z",
},
]
}
Here is a snippet of what i will like to achieve after the match
{
"_id": "609927e31233700004370cfb",
"title": "Hello World",
"createdAt": "2021-05-10T12:32:35.799Z",
"updatedAt": "2021-05-10T15:58:59.149Z",
"updates": []
},
{
"_id": "60940ad73ced476b2d0b3626",
"createdAt": "2021-05-06T15:27:19.814Z",
"updatedAt": "2021-05-10T12:49:08.167Z",
"updates": [
{
"_id": "60952c0ed31c6283f302eb23",
"post_id": "60940ad73ced476b2d0b3626",
"description": "This is an update description",
"createdAt": "2021-05-07T12:01:18.815Z",
"updatedAt": "2021-05-07T12:01:18.815Z",
},
]
},
I am trying to get the results without the section where update description is not "All updates completed
Any help here please, MondoDb version is 4+
You can use $filter
$facet to categorize incoming doucment into two. 1. updates == empty array and 2. update != empty array
$redact use to keep or eliminate the document based on the condition we give
$concatArray to combined to both arrays which were produced after $facet
$unwind to deconstruct the array
$replaceRoot to make to root
He is the script
db.collection.aggregate([
{
"$facet": {
"emptyUpdates": [
{
"$match": {
$expr: { $eq: [ "$updates", [] ] }
}
}
],
"withoutUpdate": [
{
"$match": {
$expr: { $ne: [ "$updates", [] ] }
}
},
{
"$redact": {
"$cond": [
{
"$anyElementTrue": {
"$filter": {
"input": "$updates",
"cond": {
$eq: [ "$$this.description","All updates completed" ]
}
}
}
},
"$$PRUNE",
"$$KEEP",
]
}
}
]
}
},
{
"$project": {
combined: {
"$concatArrays": ["$emptyUpdates", "$withoutUpdate" ]
}
}
},
{ "$unwind": "$combined" },
{
"$replaceRoot": {"newRoot": "$combined" }
}
])
Working Mongo playground
Let me know anything goes wrong
I have two different collections (example below) methods & items. As for now, I'm using pre 3.6 vanilla aggregation query for $lookup:
MongoPlayground Example
{
$lookup: {
from: "items",
localField: "reagents._id",
foreignField: "_id",
as: "reagent_items"
}
}
The problem is that if I am using it, I miss quantity field (from methods.reagents embedded) during $lookup stage from original collection. For now, I return quantity right after lookup but as I heard, Mongo introduced from 3.6 a new syntax for lookup queries, so the question is:
Can it solve my problem for receiving the following results:
{
"_id": 1,
"name": "Test",
"reagent_items": [ // <= the exact schema of what I need after lookup
{
"_id": 1,
"name": "ItemOne",
"other": "field",
"quantity": 2 //quantity field from original {array of objects} after lookup
},
{
"_id": 2,
"name": "ItemTwo",
"other": "field",
"quantity": 4 //quantity field from original {array of objects} after lookup
}
],
"reagents": [ //original reagents field here just for example, we could remove it
{
"_id": 1,
"quantity": 2
},
{
"_id": 2,
"quantity": 4
}
]
}
methods
{
"_id": 1,
"name": "Test",
"reagents": [
{
_id: 1,
quantity: 2
},
{
_id: 2,
quantity: 4
}
]
}
items
{
"_id": 1,
"name": "ItemOne",
"other": "field"
},
{
"_id": 2,
"name": "ItemTwo",
"other": "field"
}
Use $map along with $arrayElemAt to find corresponding reagent for each reagent_items and the apply $mergeObjects to get one object:
db.methods.aggregate([
{
$lookup: {
from: "items",
localField: "reagents._id",
foreignField: "_id",
as: "reagent_items"
}
},
{
$project: {
_id:1,
name: 1,
reagents: 1,
reagent_items: {
$map: {
input: "$reagent_items",
as: "ri",
in: {
$mergeObjects: [
"$$ri",
{
$arrayElemAt: [ { $filter: { input: "$reagents", cond: { $eq: [ "$$this._id", "$$ri._id" ] } } }, 0 ]
}
]
}
}
}
}
}
])
Mongo Playground