Map array result in mongodb aggregation - arrays

I have the following MongoDB query:
const vaccination = await Schedule.aggregate([
{ $match: { status: ScheduleStatus.Published } },
{ "$unwind": { "path": "$vaccines", "preserveNullAndEmptyArrays": true } },
{
"$group": {
"_id": "$vaccines.vaccine",
"count": { "$sum": "$vaccines.stok" },
}
},
{
$lookup: { from: 'vaccines', localField: '_id', foreignField: '_id', as: 'vaccine' },
},
{
$project: {
"count": 1,
"vaccine": { "$arrayElemAt": ["$vaccine.name", 0] }
}
}
]);
and return the following results :
[
{
"_id": "61efd8a812432135c08a748d",
"count": 20,
"vaccine": "Sinovac"
}
]
is there a way I can make the output to be an array of values like:
[["Sinovac",20]]
Thanks sorry for my bad english

So you can't get the exact structure you asked for, by definition the aggregation framework returns an array of "documents", a document is in the form of {key: value}, What you can do however is return the following structure:
[
{
"values": [
"61efd8a812432135c08a748d",
20,
"Sinovac"
]
}
]
With this pipeline:
db.collection.aggregate([
{
$project: {
_id: 0,
values: {
$map: {
input: {
"$objectToArray": "$$ROOT"
},
as: "item",
in: "$$item.v"
}
}
}
}
])
Mongo Playground

Related

Remove 3 similar items from an array using mongodb

I have this document and i need remove only 3 items with name "Test" using only 1 request.
{ "_id" : 1, "items" : ["Test, "Test", "Test", "Test", "Test", "Sword", "Sword]}
It must become { "_id" : 1, "items" : ["Test", "Test", "Sword", "Sword]} after request.
Maybe something like this:
db.collection.aggregate([
{
"$unwind": "$items"
},
{
$group: {
_id: "$items",
cnt: {
$sum: 1
},
it: {
$push: "$items"
},
origid: {
"$first": "$_id"
}
}
},
{
$project: {
_id: "$origid",
items: {
$slice: [
"$it",
0,
2
]
}
}
},
{
$group: {
_id: "$_id",
items: {
$push: "$items"
}
}
},
{
$addFields: {
items: {
"$reduce": {
"input": "$items",
"initialValue": [],
"in": {
"$concatArrays": [
"$$this",
"$$value"
]
}
}
}
}
}
])
Explained:
unwind the items array so you can group after
group by item so you get the repeating values
project with slice to remove more then 2x items in array
group to form back the document but without the >2 repeating values
project with concatArrays to concat arrays of items.
Playground
You can use this aggregation to get the desired result. The query uses the $reduce Aggregate Array Operator to iterate over the items array, and discards the first 3 matching "Test" items.
db.collection.aggregate([
{
$set: {
items: {
$reduce: {
input: "$items",
initialValue: { result: [], count: 0 },
in: {
$cond: [ { $and: [ { $eq: [ "$$this", "Test" ] }, { $lt: [ "$$value.count", 3 ] } ] },
{ result: "$$value.result", count: { $add: [ "$$value.count", 1 ] } },
{ result: { $concatArrays: [ "$$value.result", [ "$$this" ] ] }, count: "$$value.count" }
]
}
}
}
}
},
{
$set: {
items: "$items.result"
}
}
])

Mongodb use $elemMatch into $filter

I have a document like this:
I need to return the documents and filter the nested array (lessons) where any item of subLessons into input array
[{
"_id": {
"$oid": "6081fedbee5d133dbffb42eb"
},
"name": "my quiz",
"lessons": [
{
"_id": "460c42e1-b0b7-437e-ab63-c59cce8ced0d",
"name": "section",
"subLesson": [
{
"$oid": "6081fed9ee5d133dbffb3cba"
},
{
"$oid": "6081fed9ee5d133dbffb3cc0"
}
]
},
{
"_id": "f7b5c95f-1a68-42ca-880c-22ef3831ff03",
"name": "ffff",
"subLesson": [
{
"$oid": "6081fed9ee5d133dbffb3cbb"
}
]
}
]
}
}]
I wrote the following query but it does not work. I do not know how to use $elemMatch in $filter
db.collection.aggregate([
{
"$project": {
_id: 1,
lessons: {
$filter: {
"input": "$lessons",
"as": "lesson",
"cond": {
"$$lesson.subLesson": {
"$elemMatch": {
"$in": [
ObjectId("6081fed9ee5d133dbffb3cba")
]
}
}
}
}
}
}
}
])
I am trying to find the record such that the result looks like the following.
[{
"_id": {
"$oid": "6081fedbee5d133dbffb42eb"
},
"lessons": [
{
"_id": "460c42e1-b0b7-437e-ab63-c59cce8ced0d",
"name": "zzzz",
"subLesson": [
{
"$oid": "6081fed9ee5d133dbffb3cba"
},
{
"$oid": "6081fed9ee5d133dbffb3cc0"
}
]
}
]
},
}]
Can anyone please help out to understand how can I make this work
thanks
You can use $in directly
db.collection.aggregate([
{
$project: {
lessons: {
$filter: {
input: "$lessons",
cond: {
$in: [ ObjectId("6081fed9ee5d133dbffb3cba"), "$$this.subLesson" ]
}
}
}
}
}
])
Working Mongo playground
Update 1
db.collection.aggregate([
{ "$unwind": "$lessons" },
{
"$match": {
"lessons.subLesson": {
$in: [ ObjectId("6081fed9ee5d133dbffb3cba"), ObjectId("6081fed9ee5d133dbffb3cbb") ]
}
}
},
{
$group: {
_id: "$_id",
name: { $first: "$name" },
lessons: { $push: "$lessons" }
}
}
])
Mongo Playground

mongoose aggregate how to map multiple collections into one Array

I have four different collections. From which three are connected to one:
Collection_A = {
_id: 1
name: A
includes: [
{
_id: 1,
includes_id: 222,
},
{
_id: 2,
includes_id: 333
}
]
}
Collection_B = {
_id: 222,
type: Computer,
name: Computer,
ref_id: 1
}
Collection_C = {
_id: 333,
type: Human,
name: Human,
ref_id: 1
}
Collection_D = {
_id: 444,
type: Animal,
name: Animal,
ref_id: 1
}
So collection A can include Collection B, C and D in the includes object. It includes minimum one of the collections.
So in the includes object in Collection A is the includes_id, which is the _id in Collection B, C and D.
The _id in Collection A is the ref_id in Collection B, C and D.
What my problem right now is, that aggregate takes only the last mapped collection.
My code right now is following:
Collection_A.aggregate([
{
$lookup: {
from: "collectionb",
localField: "includes.includes_id",
foreignField: "_id",
as: "colb",
},
},
{
$lookup: {
from: "collectionc",
localField: "includes.includes_id",
foreignField: "_id",
as: "colc",
},
},
{
$project: {
_id: 1,
status: 1,
type: 1,
includes_list: {
$map: {
input: "$includes",
as: "i",
in: {
$arrayElemAt: [
{
$filter: {
input: "$colb",
cond: {
$eq: ["$$this._id", "$$i.includes_id"],
},
},
},
0,
],
$arrayElemAt: [
{
$filter: {
input: "$colc",
cond: {
$eq: ["$$this._id", "$$i.includes_id"],
},
},
},
0,
],
},
},
},
},
},
]);
I tried to make the $lookup as the same on every lookup, but so it only took the last looked up data, and the others where shown as null.
So I made $lookup as unique, and put two ins in map, but then also the last looked up data was shown, and the others where null.
When I do the mapping like that:
includes_list: {
$map: {
input: "$icludes",
as: "i",
in: {
{
Col_A : {
$arrayElemAt: [
{
$filter: {
input: "$A",
cond: {
$eq: ["$$this._id", "$$i.includes"],
},
},
},
0,
],
},
Col_B : {
$arrayElemAt: [
{
$filter: {
input: "$B",
cond: {
$eq: ["$$this._id", "$$i.includes"],
},
},
},
0,
],
}
}
},
},
}
It workes. But not with the right output, because I need includes_list within one array.
My desired output is like following:
{
includes: [
{
_id: 1,
name: Computer,
includes_list: [
{
_id: 222,
type: Computer,
name: Computer,
ref_id: 1
},
{
_id: 333,
type: Human,
name: Human,
ref_id: 1
},
]
},
{
_id: 2,
name: Animal,
includes_list: [
{
_id: 333,
type: Human,
name: Human,
ref_id: 2
},
]
}
]
}
Would appreciate any help!
For this kind of situation,
$facet help to categorize the incoming data
db.Collection_A.aggregate([
{ $unwind: "$includes },
{
"$facet": {
"joinB": [
{
"$lookup": {
"from": "Collection_B", "localField": "includes.includes_id",
"foreignField": "_id", "as": "includes.includes_list"
}
},
{
"$group": {
"_id": "$_id",
"name": { "$first": "$name" },
includes: { $push: "$includes" }
}
}
],
"joinC": [
{
"$lookup": {
"from": "Collection_C", "localField": "includes.includes_id",
"foreignField": "_id", "as": "includes.includes_list"
}
},
{
"$group": {
"_id": "$_id",
"name": { "$first": "$name" },
includes: { $push: "$includes" }
}
}
],
"joinD": [
{
"$lookup": {
"from": "Collection_D", "localField": "includes.includes_id",
"foreignField": "_id", "as": "includes.includes_list"
}
},
{
"$group": {
"_id": "$_id",
"name": { "$first": "$name" },
includes: { $push: "$includes" }
}
}
],
}
},
{
$project: {
combined: {
"$concatArrays": [ "$joinB", "$joinC", "$joinD" ]
}
}
},
{ "$unwind": "$combined" },
{
"$replaceRoot": { "newRoot": "$combined" }
},
{
"$project": {
_id: 1,
name: 1,
includes: {
$filter: {
input: "$includes",
cond: {
$ne: [ "$$this.includes_list",[] ]
}
}
}
}
}
])
Working Mongo playground
Note: I feel this is kind of Anti pattern you follow. If you are in early stage of the project, better change the structure if I'm not mistaken.

How to divide documents values into same document, however key name of the divisor is same in MongoDB

Explanation: want to divide the name: Alex , name: petr value by name: hr value.
name: Alex , name: petr and name: hr are my parameters names.
also want to see the value of name: hr in the output document.
[
{
"name": "Alex",
"value": 65
},
{
"name": "petr",
"value": 8
},
{
"name": "hr",
"value": 20
}
]
Expected Output :
[
{
"name": "Alex/hr",
"value": 3.25
},
{
"name": "petr/hr",
"value": 0.4
},
{
"name": "hr",
"value": 20
}
]
Demo - https://mongoplayground.net/p/38G_Loo8V86
Use $facet
db.collection.aggregate([
{
$facet: {
hrVal: [
{ $match: { name: "hr" } }, // filter
{ $project: { _id: 0, value: 1 } } // take only value
],
allValues: [] // all documents or you can add match pipeline to filter here
}
},
{ $unwind: "$hrVal" }, // break into individual documents - now every document will have hrVal.value
{ $unwind: "$allValues" }, // break into individual documents
{
$set: { //
"allValues": {
"$cond": [
{ $eq: [ "$allValues.name", "hr" ] }, // condition
"$allValues", // true
{ // false
_id: "$allValues._id",
name: { "$concat": [ "$allValues.name", "/hr" ] }, // set name
value: { "$divide": [ "$allValues.value", "$hrVal.value" ] } // divide by hr value
}
]
}
}
},
{ $replaceRoot: { "newRoot": "$allValues" } } // reset to orignal document shape
])
$lookup with same collection and match for name: hr and return single result
$unwind deconstruct hr array
$project to check condition if name is hr then return current name and value of not then concat name using $concat and divide value by $divide
$project to move doc object to root
db.collection.aggregate([
{
$lookup: {
from: "collection",
pipeline: [
{ $match: { name: "hr" } },
{ $limit: 1 }
],
as: "hr"
}
},
{ $unwind: "$hr" },
{
$project: {
doc: {
$cond: [
{ $eq: ["$name", "hr"] },
{
name: "$name",
value: "$value"
},
{
name: { $concat: ["$name","/","$hr.name"] },
value: { $divide: ["$value", "$hr.value"] }
}
]
}
}
},
{
$project: {
name: "$doc.name",
value: "$doc.value"
}
}
])
Playground
Second option without lookup,
$facet to separate both result
$map to iterate loop of result array and check name is hr then concat name and divide value otherwise return same
$unwind deconstruct result array
$project to show fields
db.collection.aggregate([
{
$facet: {
result: [{ $match: {} }],
hr: [{ $match: { name: "hr" } }]
}
},
{
$project: {
result: {
$map: {
input: "$result",
in: {
$cond: [
{ $eq: ["$$this.name", "hr"] },
"$$this",
{
name: { $concat: ["$$this.name", "/", { $first: "$hr.name" }] },
value: { $divide: ["$$this.value", { $first: "$hr.value" }] }
}
]
}
}
}
}
},
{ $unwind: "$result" },
{
$project: {
name: "$result.name",
value: "$result.value"
}
}
])
Playground

How to group an array of subdocuments by multiple fields?

I have spent the better part of the day on this and am now out of ideas. Here is my collection:
[
{
"_id": "ID_XXX",
"logs": [
{
"lead_id": 123,
"list_id": "list_44",
"order_id": "order_1"
},
{
"lead_id": 124,
"list_id": "list_44",
"order_id": "order_2"
}
]
},
{
"_id": "ID_YYY",
"logs": [
{
"lead_id": 125,
"list_id": "list_44",
"order_id": "order_2"
},
{
"lead_id": 126,
"list_id": "list_44",
"order_id": "order_2"
},
{
"lead_id": 127,
"list_id": "list_44",
"order_id": "order_3"
},
{
"lead_id": 128,
"list_id": "list_66",
"order_id": "order_3"
}
]
}
]
I'm just trying to get the counts for list_id and order_id while preserving the _id of the document they are in. Here is my desired output:
[
{
"_id": "ID_XXX",
"counts": [
{
"lists": {"list_44": 2},
},
{
"orders": {"order_1": 1, "order_2": 1}
}
]
},
{
"_id": "ID_YYY",
"counts": [
{
"lists": {"list_44": 3, "list_66": 1},
},
{
"orders": {"order_2": 2, "order_3": 2}
}
]
}
]
I have tried way too many aggregate variations to list here, but the latest is this:
db.collection.aggregate([
{
$unwind: "$logs"
},
{
$group: {
_id: "$_id",
lists: {
$push: "$logs.list_id"
},
orders: {
$push: "$logs.order_id"
}
}
}
])
Which does not give me what I want. Can anyone point me in the right direction? Here is the playground link: https://mongoplayground.net/p/f-jk7lbSrJ0
$reduce to iterate loop of logs, convert logs object to array in k(key) v(value) format using $objectToArray, $concatArrays with initialValue in $reduce,
$filter above reduce result as input and filter required fields from logs
$unwind deconstruct logs array
db.collection.aggregate([
{
$addFields: {
logs: {
$filter: {
input: {
$reduce: {
input: "$logs",
initialValue: [],
in: { $concatArrays: ["$$value", { $objectToArray: "$$this" }] }
}
},
cond: { $in: ["$$this.k", ["list_id", "order_id"]] }
}
}
}
},
{ $unwind: "$logs" },
$group by _id and logs object and get the total count using $sum
{
$group: {
_id: {
_id: "$_id",
logs: "$logs"
},
counts: { $sum: 1 }
}
},
$group by _id and make lists array if logs.k is list_id and return in k and v format otherwise $$REMOVE, same as for orders make an array of order on the base of order_id
$addFields to convert lists array from k and v format to object format using $arrayToObjectand same as fororders` array
{
$group: {
_id: "$_id._id",
lists: {
$push: {
$cond: [
{ $eq: ["$_id.logs.k", "list_id"] },
{
k: "$_id.logs.v",
v: "$counts"
},
"$$REMOVE"
]
}
},
orders: {
$push: {
$cond: [
{ $eq: ["$_id.logs.k", "order_id"] },
{
k: "$_id.logs.v",
v: "$counts"
},
"$$REMOVE"
]
}
}
}
},
{
$addFields: {
lists: { $arrayToObject: "$lists" },
orders: { $arrayToObject: "$orders" }
}
}
])
Playground

Resources