MongoDB Aggregation - Dynamic `$addField` - javascript

I have the following object in the format _id: userID and a Mongoose query:
const obj = {
111: "222",
333: "444",
555: "666"
};
Model.aggregate([
{
$addFields: {
userID: ???
}
}
]);
I need a way to add a userID field to each document based on the object. So let's say the collection has this data:
[
{
_id: "111",
...
},
{
_id: "333",
...
},
{
_id: "555",
...
}
]
The document should end up looking like this:
[
{
_id: "111",
userID: "222",
...
},
{
_id: "333",
userID: "444",
...
},
{
_id: "555",
userID: "666",
...
}
]
How can I do this?

You need to start with $objectToArray to get your dynamic object as an array of keys and values. Then you can use $filter to find matching pair, $arrayElemAt to get single element:
db.collection.aggregate([
{
$addFields: {
userId: {
$let: {
vars: {
match: {
$arrayElemAt: [
{
$filter: {
input: { $objectToArray: { 111: "222", 333: "444", 555: "666" } },
cond: { $eq: [ "$$this.k", { $toString: "$_id" } ] }
}
},
0
]
}
},
in: "$$match.v"
}
}
}
}
])
Mongo Playground

Related

How to return nested document with Mongoose?

If I have this collection
[
{
"_id": "637cbf94b4741277c3b53c6c",
"text": "outter",
"username": "test1",
"address": [
{
"text": "inner",
"username": "test2",
"_id": "637cbf94b4741277c3b53c6e"
}
],
"__v": 0
}
]
and would like to search for the nested document by _id and return all of the nested document. If I do
db.collection.find({
_id: "637cbf94b4741277c3b53c6c"
},
{
address: {
$eq: {
_id: "637cbf94b4741277c3b53c6e"
}
}
})
I get
query failed: (Location16020) Expression $eq takes exactly 2 arguments. 1 were passed in.
Playground link
Question
Can anyone see what I am doing wrong?
use $elemMatch and also you have extra unneeded brackets. try
db.collection.find({
_id: "637cbf94b4741277c3b53c6c",
address: {
$elemMatch: {
_id: "637cbf94b4741277c3b53c6e"
}
}
})
Edit: if you only want to return the address add projection like this
db.collection.find({
_id: "637cbf94b4741277c3b53c6c",
address: {
$elemMatch: {
_id: "637cbf94b4741277c3b53c6e"
}
}
},
{
_id: 0,
address: 1
})
One option is to use find:
db.collection.find({},
{
_id: 0,
address: {
$elemMatch: {
_id: "637cbf94b4741277c3b53c6e"
}
}
})
See how it works on the playground example
The other option is to use aggregation pipeline:
db.collection.aggregate([
{
$match: {
$expr: {
$in: [
"637cbf94b4741277c3b53c62",
"$address._id"
]
}
}
},
{
$replaceRoot: {
newRoot: {
$first: {
$filter: {
input: "$address",
cond: {
$eq: [
"$$this._id",
"637cbf94b4741277c3b53c6e"
]
}
}
}
}
}
}
])
See how it works on the playground example

Getting data from a different collection while performing aggregation on a different collection

I have a collection named Vote that looks like the following:
{
postId: "1",
comment:{
text_sentiment: "positive",
topic: "A"
}
}, // DOC-1
{
postId: "2",
comment:{
text_sentiment: "negative",
topic: "A"
}
}, // DOC-2
{
postId: "3",
comment:{
text_sentiment: "positive",
topic: "B"
}
},..//DOC-3 ..
and a collection named post which looks as follows?
{
_id: "1",
category: "a"
},
{
_id: "2",
category: "b",
}
I want to do an aggregation on this collection such that it returns the following structure. (which also takes into account the other post collection`
[
{
_id: "hash",
topic: "A",
topicOccurance: 2,
sentiment: {
positive: 1,
negative: 1,
neutral: 0
},
postIds: [1,2],
categories: [a,b]
},
..
]
I created the following aggregation:
db.Vote.aggregate([
{
$match: {
surveyId: "e6d38e1ecd",
"comment.topic": {
$exists: 1
},
}
},
{
$group: {
_id: {
topic: "$comment.topic",
text_sentiment: "$comment.text_sentiment"
},
total: {
$sum: 1
},
postIds: {
$push: "$postId"
}
}
},
{
$group: {
_id: "$_id.topic",
total: {
$sum: "$total"
},
text_sentiments: {
$push: {
k: "$_id.text_sentiment",
v: "$total"
}
},
postIds: {
"$push": "$postIds"
}
}
},
{
$project: {
topic: "$_id",
topicOccurance: "$total",
sentiment: {
"$arrayToObject": "$text_sentiments"
},
postIds: {
$reduce: {
input: "$postIds",
initialValue: [],
in: {
$concatArrays: [
"$$value",
"$$this"
]
}
}
}
}
},
{
$sort: {
"topicOccurance": -1
}
}
])
This works fine but I do not know, how to also get categories which exist in a separate collection named posts. How can I query a different collection while performing aggregation?
A simple $lookup will suffice:
db.Vote.aggregate([
{
$match: {
"comment.topic": {
$exists: 1
},
}
},
{
$group: {
_id: {
topic: "$comment.topic",
text_sentiment: "$comment.text_sentiment"
},
total: {
$sum: 1
},
postIds: {
$push: "$postId"
}
}
},
{
$group: {
_id: "$_id.topic",
total: {
$sum: "$total"
},
text_sentiments: {
$push: {
k: "$_id.text_sentiment",
v: "$total"
}
},
postIds: {
"$push": "$postIds"
}
}
},
{
$project: {
topic: "$_id",
topicOccurance: "$total",
sentiment: {
"$arrayToObject": "$text_sentiments"
},
postIds: {
$reduce: {
input: "$postIds",
initialValue: [],
in: {
$concatArrays: [
"$$value",
"$$this"
]
}
}
}
}
},
{
$sort: {
"topicOccurance": -1
}
},
{
$lookup: {
from: "post",
localField: "postIds",
foreignField: "_id",
as: "categories"
}
},
{
$addFields: {
categories: {
"$setUnion": {
$map: {
input: "$categories",
in: "$$this.category"
}
}
}
}
}
])
Mongo Playground

How to return sorted array from object from MongoDB document?

I want to return an array, which is a property inside my mongo model/document, and I want that array to be sorted.
My MongoDB document looks like:
_id: ObjectID("6248e49c88ff07aedee8c000")
title: "School"
items: [
{
sort: 2,
name: "homework"
},
{
sort: 1,
name: "exam"
},
{
sort: 3,
name: "essay"
},
]
And I'm trying to return:
items: [
{
sort: 1,
name: "exam"
},
{
sort: 2,
name: "homework"
},
{
sort: 3,
name: "essay"
}
]
I have tried aggregation:
app.get("/api/v1/lists/:id", async (req,res) =>{
List.aggregate([{
"$match" :{"_id": req.params.id}
},{
"$unwind" : "$items"
} , {
"$sort" : {"sort": 1}
}
], (err, items)=>{
res.json(items)
})
}
Mongo Playground reference
Since $unwind returns the arrays as objects, we are using the $group to push the objects back into the items array
db.collection.aggregate([
{
$unwind: "$items"
},
{
$sort: {
"items.sort": 1
}
},
{
$group: {
_id: "$_id",
items: {
$push: "$items"
}
}
},
])
Output -
[
{
"_id": 1.212112e+06,
"items": [
{
"name": "exam",
"sort": 1
},
{
"name": "homework",
"sort": 2
},
{
"name": "essay",
"sort": 3
}
]
}
]

MongoDB - $addToSet for an Array?

Structure:
[
{
"_id": "12",
"title": "Vanella Icream",
"contain":"sugar",
"details": [
{
"flavour": "Vanella"
},
{
"weight": "6KG"
}
]
},
{
"_id": "3",
"title": "Pretzels",
"contain":"salt",
"details": [
{
"flavour": "Wheat"
},
{
"weight": "2KG"
}
]
}
]
Expected Output
details:
{
"flavour": [
"Vanella",
"Wheat",
],
"weight": [
"6KG",
"2KG",
]
}
More Important: I've to do this without details.flavour or details.weight.
I’ve tried with $addToSet, $filter $group but I’m not getting results like that. anyone please suggest.
Mongo Playground: https://mongoplayground.net/p/SXw3jaRDzLy
$project to show required field
$unwind deconstruct the details array
$objectToArray convert details object to array key-value format
$unwind deconstruct the details array
$group by details key and construct the array of values
$group by null and construct the array of details in key-value format
$arrayToObject convert above result from key-value format array of object to an object
db.collection.aggregate([
{
$project: {
_id: 0,
details: 1
}
},
{ $unwind: "$details" },
{
$project: {
details: { $objectToArray: "$details" }
}
},
{ $unwind: "$details" },
{
$group: {
_id: "$details.k",
v: { $addToSet: "$details.v" }
}
},
{
$group: {
_id: null,
details: {
$push: { k: "$_id", v: "$v" }
}
}
},
{
$project: {
_id: 0,
details: { $arrayToObject: "$details" }
}
}
])
Playground

How to convert array to string in mongodb

I have below schema
{
id: 123,
values:[
{valueId: "12444", name: "asd"},
{valueId: "555", name: "www"},
]
}
i want to convert it into (combine name into single string)
{
id: 123,
values: "asdwww"
}
i have tried below aggregate which puts all name value in an array
$project: {
attributes: {
"$map": {
"input": "$attributes",
"as": "attr",
"in": {
"id": "$$attr.id",
"values": "$$attr.values.name"
}
}
}
},
which makes it into
{
id: 123,
values:[
"asd",
"www"
]
}
i want to have values as single string value as "asd,www" or "asdwww"
You need $reduce instead of $map:
db.collection.aggregate([
{
$project: {
_id: 1,
values: {
$reduce: {
input: "$values",
initialValue: "",
in: { $concat: [ "$$value", "$$this.name" ] }
}
}
}
}
])
Mongo Playground
Here's an example which shows how to handle delimiters

Categories