I have two collections. boxes and balls. A ball can be in a box:
boxes:
[{
"_id": { "$oid": "box-a" },
"name": "Box A"
},{
"_id": { "$oid": "box-b" },
"name": "Box B"
}]
balls:
[{
"_id": { "$oid": "ball-a" },
"color": "red",
"boxId": { "$oid": "box-a" }
},{
"_id": { "$oid": "ball-b" },
"color": "green",
"boxId": { "$oid": "box-a" }
}]
Now I want to query all boxes with an additional field ballColors where I get an overview how many balls of what color are in the boxes:
[{
"_id": { "$oid": "box-a" },
"name": "Box A",
"ballColors": {
"red": 1,
"green": 1,
}
},{
"_id": { "$oid": "box-b" },
"name": "Box B",
"ballColors": {}
}]
I tried to solve it with an aggregation like following:
db.boxes.aggregate([
{$lookup: {
from: "balls",
localField: "_id",
foreignField: "boxId",
as: "ballColors"
}},
{$addFields: {
ballColors: "$ballColors.color"
}}
])
...but this gives me something like that:
[{
"_id": { "$oid": "box-a" },
"name": "Box A",
"ballColors": [
"red",
"green"
]
},{
"_id": { "$oid": "box-b" },
"name": "Box B",
"ballColors": []
}]
I also did some experiments with $unwind combined with $group but I have no clue how to get those information back into the original objects...
Is there a way to count the colors in ballColors and put it in an object? Or is there another better way to do this?
$lookup with pipeline, pass _id as boxID in let, $match boxId condition
$group by color and get total count
$project to show k as color and v as count
convert key-values array to object using $arrayTOobject in $addFields
db.boxes.aggregate([
{
$lookup: {
from: "balls",
let: { boxId: "$_id" },
pipeline: [
{ $match: { $expr: { $eq: ["$$boxId", "$boxId"] } } },
{
$group: {
_id: "$color",
count: { $sum: 1 }
}
},
{
$project: {
_id: 0,
k: "$_id",
v: "$count"
}
}
],
as: "ballColors"
}
},
{ $addFields: { ballColors: { $arrayToObject: "$ballColors" } } }
])
Playground
Related
Let's say i have 2 collections
// Post collection:
[
{
"_id": "somepost1",
"author": "firstuser",
"title": "First post"
},
{
"_id": "somepost2",
"author": "firstuser",
"title": "Second post"
},
{
"_id": "somepost3",
"author": "firstuser",
"title": "Third post"
}
]
// User collection:
[
{
"_id": "firstuser",
"nickname": "John",
"posts": {
"voted": []
}
},
{
"_id": "seconduser",
"nickname": "Bob",
"posts": {
"voted": [
{
"_id": "somepost1",
"vote": "1"
},
{
"_id": "somepost3",
"vote": "-1"
}
]
}
}
]
And i need to get this result:
[
{
"_id": "somepost1",
"author": {
"_id": "firstuser",
"nickname": "John"
},
"title": "First post",
"myvote": "1"
},
{
"_id": "somepost2",
"author": {
"_id": "firstuser",
"nickname": "John"
},
"title": "Second post",
"voted": "0"
},
{
"_id": "somepost3",
"author": {
"_id": "firstuser",
"nickname": "John"
},
"title": "Third post",
"myvote": "-1"
}
]
How can i make a request with aggregation, which will display this output with dynamic _id of elements?
I have problem with using current _id of post in second $match and setting "myvote" to 0 if there are no element in "posts.voted" associated with current post.
Here what i've tried: https://mongoplayground.net/p/v70ZUioVSpQ
db.post.aggregate([
{
$match: {
author: "firstuser"
}
},
{
$lookup: {
from: "user",
localField: "author",
foreignField: "_id",
as: "author"
}
},
{
$addFields: {
author: {
$arrayElemAt: [
"$author",
0
]
}
}
},
{
$lookup: {
from: "user",
localField: "_id",
foreignField: "posts.voted._id",
as: "Results"
}
},
{
$unwind: "$Results"
},
{
$unwind: "$Results.posts.voted"
},
{
$match: {
"Results.posts.voted._id": "ID OF CURRENT POST"
}
},
{
$project: {
_id: 1,
author: {
_id: 1,
nickname: 1
},
title: 1,
myvote: "$Results.posts.voted.vote"
}
}
])
From the $match docs:
The query syntax is identical to the read operation query syntax
The query syntax does not allow usage of document values. which is what you're trying to do.
What we can do is use $expr within the $match stage, this allows us to use aggregation oprerators, thus also giving access to the document values. like so:
{
$match: {
$expr: {
$eq: ['$Results.posts.voted._id', '$_id'],
}
},
},
{
"orderNo": "123",
"bags": [{
"type": "small",
"products": [{
"id": "1",
"name": "ABC",
"returnable": true
}, {
"id": "2",
"name": "XYZ"
}
]
},{
"type": "big",
"products": [{
"id": "3",
"name": "PQR",
"returnable": true
}, {
"id": "4",
"name": "UVW"
}
]
}
]
}
I have orders collection where documents are in this format. I want to get a total count of products which has the returnable flag. e.g: for the above order the count should be 2. I am very new to MongoDB wanted to know how to write a query to find this out, I have tried few things but did not help:
this is what I tried but not worked:
db.orders.aggregate([
{ "$unwind": "$bags" },
{ "$unwind": "$bags.products" },
{ "$unwind": "$bags.products.returnable" },
{ "$group": {
"_id": "$bags.products.returnable",
"count": { "$sum": 1 }
}}
])
For inner array you can use $filter to check returnable flag and $size to get number of such items. For the outer one you can take advantage of $reduce to sum the values from inner arrays:
db.collection.aggregate([
{
$project: {
totalReturnable: {
$reduce: {
input: "$bags",
initialValue: 0,
in: {
$add: [
"$$value",
{
$size: {
$filter: {
input: "$$this.products",
as: "prod",
cond: {
$eq: [ "$$prod.returnable", true ]
}
}
}
]
}
}
}
}
}
}
])
Mongo Playground
I'm working on a hierarchical structure that stores a binary tree.
Let's say I have two collections: users and nodes.
users collection stores personal information and nodes stores the structure of the tree using the Parent References pattern: https://docs.mongodb.com/manual/tutorial/model-tree-structures-with-parent-references/
Users:
[{
"_id": {
"$oid": "600365521599912a5c814e5e"
},
"nombre": "Andres",
"correo": "oachica#gmail.com"
},{
"_id": {
"$oid": "600365e9ccf1e51b2cab341f"
},
"nombre": "Andres",
"correo": "cachi777_#hotmail.com"
},{
"_id": {
"$oid": "6004591536a40941f48121f9"
},
"nombre": "Laura",
"correo": "w.l777#hotmail.com"
},{
"_id": {
"$oid": "6004596936a40941f48121fb"
},
"nombre": "Javi",
"correo": "jocta#hotmail.com"
},{
"_id": {
"$oid": "60047cf23f3f1a0d647cb2c7"
},
"nombre": "Lina",
"correo": "lvelas#hotmail.com"
}]
nodos:
[{
"_id": {
"$oid": "60035d0a1599912a5c814e58"
},
"idUsuario": "600365521599912a5c814e5e",
"nodoPadre": ""
},{
"_id": {
"$oid": "60047e6874cab54a7088ca56"
},
"idUsuario": "600365e9ccf1e51b2cab341f",
"nodoPadre": {
"$oid": "60035d0a1599912a5c814e58"
}
},{
"_id": {
"$oid": "60047f42c89add3c20cff990"
},
"idUsuario": "6004591536a40941f48121f9",
"nodoPadre": {
"$oid": "60047e6874cab54a7088ca56"
}
},{
"_id": {
"$oid": "60047f5dc89add3c20cff991"
},
"idUsuario": "6004596936a40941f48121fb",
"nodoPadre": {
"$oid": "60047f42c89add3c20cff990"
}
},{
"_id": {
"$oid": "600480de9fd6a42b40679e6d"
},
"idUsuario": "60047cf23f3f1a0d647cb2c7",
"nodoPadre": {
"$oid": "60047f5dc89add3c20cff991"
}
}]
Each document in nodos has corresponding document in users bound by _id and idUsuario.
Generally a document in nodes collection has a parent node in the same collection bound by nodoPadre field.
I'm able to get childs of a node using $graphLookup aggregation:
As you can se I got the childs of a node. Now I need to put the personal information in each child in the result array "hijos" as shown below:
Thanks for your help.
$graphLookup as per your requirement
$unwind deconstruct hijos array
$addFields convert hijos.idUsuario to object id because its an string, if it is already in object id then remove this stage
$lookup with users collection
$unwind deconstruct hijos.idUsuario array
$addFields to remove hijos if it is blank {} object
$group by _id and reconstruct hijos array
db.nodes.aggregate([
{
"$graphLookup": {
"from": "nodes",
"startWith": "$_id",
"connectFromField": "_id",
"connectToField": "nodoPadre",
"as": "hijos",
"maxDepth": 4
}
},
{
$unwind: {
path: "$hijos",
preserveNullAndEmptyArrays: true
}
},
{ $addFields: { "hijos.idUsuario": { $toObjectId: "$hijos.idUsuario" } } },
{
"$lookup": {
"from": "users",
"localField": "hijos.idUsuario",
"foreignField": "_id",
"as": "hijos.idUsuario"
}
},
{
$unwind: {
path: "$hijos.idUsuario",
preserveNullAndEmptyArrays: true
}
},
{
$addFields: {
hijos: {
$cond: [{ $eq: ["$hijos", {}] }, "$$REMOVE", "$hijos"]
}
}
},
{
$group: {
_id: "$_id",
hijos: { $push: "$hijos" },
idUsuario: { $first: "$idUsuario" },
nodoPadre: { $first: "$nodoPadre" }
}
}
])
Playground
I have two collections name listings and moods.
listings sample:
{
"_id": ObjectId("5349b4ddd2781d08c09890f3"),
"name": "Hotel Radisson Blu",
"moods": [
ObjectId("507f1f77bcf86cd799439010"),
ObjectId("507f1f77bcf86cd799439011")
]
}
moods sample:
{
"_id": ObjectId("507f1f77bcf86cd799439011"),
"name": "Sports"
},
{
"_id": ObjectId("507f1f77bcf86cd799439010"),
"name": "Spanish Food"
},
{
"_id": ObjectId("507f1f77bcf86cd799439009"),
"name": "Action"
}
I need this record.
{
"_id": ObjectId("507f1f77bcf86cd799439011"),
"name": "Sports",
"count": 1
},
{
"_id": ObjectId("507f1f77bcf86cd799439010"),
"name": "Spanish Food",
"count": 1
},
{
"_id": ObjectId("507f1f77bcf86cd799439009"),
"name": "Action",
"count": 0
}
I need this type of record. I have no idea about aggregate.
You can do it using aggregate(),
$lookup to join collection listings
$match pipeline to check moods _id in listings field moods array
db.moods.aggregate([
{
"$lookup": {
"from": "listings",
"as": "count",
let: { id: "$_id" },
pipeline: [
{
"$match": {
"$expr": { "$in": ["$$id", "$moods"] }
}
}
]
}
},
$addFields to add count on the base of $size of array count that we got from above lookup
{
$addFields: {
count: { $size: "$count" }
}
}
])
Playground
did this work:
db.collection.aggrate().count()
Try to combine the functions, it might work.
I have a pipeline that works great for what I need... but I think there is some redundant data that can be removed from the pipeline.
Expected output
This is what I want the output to look like
{
"_id": "5ecee2189fdd1b0004056936",
"name": "Mike",
"history": [
{
"_id": "5ecb263c166b8500047c1411",
"what": "Log IN"
},
{
"_id": "5ecb263c166b8500047c1422",
"what": "Log OUT"
}
]
}
Current output
This is what the output currently looks like
{
"docs": [
{
"_id": "5ecee2189fdd1b0004056936",
"name": "Mike",
"history": {
"_id": "5ecb263c166b8500047c1411",
"what": "Log IN"
},
"historyIndex": 0
},
{
"_id": "5ecee2189fdd1b0004056936",
"name": "Mike",
"history": {
"_id": "5ecb263c166b8500047c1422",
"what": "Log OUT"
},
"historyIndex": 1
}
]
}
User doc
In real life there will be more users than this... of course...
{
"_id": "5ecee2189fdd1b0004056936",
"name": "Mike",
}
History docs
again, to make it simple, I am keeping data short
[
{
"_id": "5ecb263c166b8500047c1411",
"userId": "5ecee2189fdd1b0004056936",
"what": "Log IN"
},
{
"_id": "5ecb263c166b8500047c1422",
"userId": "5ecee2189fdd1b0004056999",
"what": "Log IN"
},
{
"_id": "5ecb263c166b8500047c1433",
"userId": "5ecee2189fdd1b0004056936",
"what": "Log OUT"
},
{
"_id": "5ecb263c166b8500047c1444",
"userId": "5ecee2189fdd1b0004056999",
"what": "Log OUT"
}
]
mongoose-aggregate-paginate-v2 middleware
I am also using mongoose-aggregate-paginate-v2, but I don't think that is my issue, but it definitely comes into play when the results are returned. it needs to have the docs flattened so it can count and paginate them:
"totalDocs": 941,
"limit": 500,
"page": 1,
"totalPages": 2,
"pagingCounter": 1,
"hasPrevPage": false,
"hasNextPage": true,
"prevPage": null,
"nextPage": 2
Pipeline
Here is my pipeline
var agg_match = {
$match:
{
_id: mongoose.Types.ObjectId(userId)
}
};
var agg_lookup = {
$lookup: {
from: 'it_userhistories',
localField: '_id',
foreignField: 'userId',
as: 'history'
}
}
var agg_unwind = {
$unwind: {
path: "$history",
preserveNullAndEmptyArrays: true,
includeArrayIndex: 'historyIndex',
}
}
var agg = [
agg_match,
agg_lookup,
agg_unwind,
agg_project,
];
var pageAndLimit = {
page:page,
limit:limit
}
User.aggregatePaginate(myAggregate, pageAndLimit)
You can use $map operator to do this. Following query will be helpful (I have not included the match stage in the pipeline, you can easily include it):
db.user.aggregate([
{
$lookup: {
from: "history",
localField: "_id",
foreignField: "userId",
as: "history"
}
},
{
$project: {
name: 1,
history: {
$map: {
input: "$history",
as: "h",
in: {
_id: "$$h._id",
what: "$$h.what"
}
}
}
}
}
])
MongoPLayGroundLink