i have an a nested array. preformed aggregation to make the subarray the new root.
Category.aggregate([{$unwind: "$SubCats"}, { $replaceRoot: {newRoot: '$SubCats'}} ])
now i need to find by id.
using this would return empty reuslts:
Category.aggregate([{$unwind: "$SubCats"}, { $replaceRoot: {newRoot: '$SubCats'}}, {$match: {_id: `${req.params.id}`}} ])
using $elemMatch is not supported for my atlas tier. and using findById() gives this erro 'Category.aggregate(...).findById is not a function'
array:
[
{
_id: '61cae5daf5bfbebd7cf748ee'
title: 'category 1',
SubCats: [
{
_id: '61cae5daf5bfbebd7cf748ef'
name: 'subcat 1',
image: '/assets/images/vr-box-6203301_1920.jpg',
},
{
_id: '61cae5daf5bfbebd7cf748fb'
name: 'subcat 2',
image: '/assets/images/galaxy-s20_highlights_kv_00.jpg',
},
]
},
]
after aggregation:
[
{
_id: '61cae5daf5bfbebd7cf748ef'
name: 'subcat 1',
image: '/assets/images/vr-box-6203301_1920.jpg',
},
{
_id: '61cae5daf5bfbebd7cf748fb'
name: 'subcat 2',
image: '/assets/images/galaxy-s20_highlights_kv_00.jpg',
},
]
so i needed to install mongodb from npm i didnt have it installed intially because i was conncting to a cloud mongodb databasse.
so
npm i mongodb
then import
import mongodb from 'mongodb'
const {ObjectId} = mongodb
and then this is what worked:
Category.aggregate([{$unwind: "$SubCats"}, { $replaceRoot: {newRoot: '$SubCats'}}, {$match: {_id: ObjectId(req.params.id)} } ])
Why not just move the $match stage to be at the start of the pipeline? it will also improve the pipeline's performance as you won't unwind and replace root on many irrelevant documents:
Category.aggregate([ {$match: {_id: `${req.params.id}`}}, {$unwind: "$SubCats"}, { $replaceRoot: {newRoot: '$SubCats'}} ])
If for some reason you don't want to do this then you have to preserve the original id field throughout the pipeline, specifically through the $replaceRoot stage, like so:
Category.aggregate([
{
$unwind: "$SubCats"
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
'$SubCats',
{ original_id: "$_id" }
]
}
}
},
{
$match: {original_id: `${req.params.id}`}
}
])
Field _id is an ObjectId, thus the query must be like this:
Category.aggregate([
{ $unwind: "$SubCats" },
{ $replaceRoot: {newRoot: '$SubCats'} },
{ $match: {_id: ObjectId(`${req.params.id}`) } }
])
But you simply use
Category.aggregate([
{ $match: {"SubCats._id": ObjectId(`${req.params.id}`) } }
])
or use $filter
Category.aggregate([
{
$set: {
SubCats: {
$filter: {
input: "$SubCats",
cond: { $eq: ["$$this._id", ObjectId(`${req.params.id}`)] }
}
}
}
}
])
Related
consider the following array of documents returned from the aggregation:
[
{
id: '#abcdefg123',
title: 'Doc 1'
...
},
{
id: '#abcdefg124',
title: 'Doc 2'
...
},
{
id: '#abcdefg125',
title: 'Doc 3'
...
}
]
Each document contains like 20 fields...
I would like to get the following output:
{
edges: [
{
node: {
id: '#abcdefg123',
title: 'Doc 1',
...
}
cursor: '#abcdefg123'
},
{
node: {
id: '#abcdefg124',
title: 'Doc 2',
...
}
cursor: '#abcdefg124'
},
{
node: {
id: '#abcdefg125',
title: 'Doc 3',
...
}
cursor: '#abcdefg125'
}
]
}
I've tried to $group a new field edges with $push as k, v pair of '$ROOT' but with no such desired result.
And I've tried to use $map on something like '$$ROOT' as input and trying to return object
{
node: '$item',
cursor: '$item._id'
}
Of course with not luck either.
I don't want to iterate over the results and populate the right object shape of data after the aggregation. Considering the performance would drop.
I would prefer to achieve the desired result inside of aggregation pipeline.
Thanks in advance.
db.collection.aggregate([
{
$project: {
"node": "$$ROOT",
"cursor": "$id"
}
},
{
$group: {
"_id": null,
"edges": {
$push: "$$ROOT"
}
}
}
]);
Mongo Playground
I'm using the aggregate framework to query a collection and create an array of active players (up until the last $lookup) after which I'm trying to use $lookup and $pipeline to select all the players from another collection (users) that are not present inside the activeUsers array.
Is there any way of doing this with my current setup?
Game.aggregate[{
$match: {
date: {
$gte: ISODate('2021-04-10T00:00:00.355Z')
},
gameStatus: 'played'
}
}, {
$unwind: {
path: '$players',
preserveNullAndEmptyArrays: false
}
}, {
$group: {
_id: '$players'
}
}, {
$group: {
_id: null,
activeUsers: {
$push: '$_id'
}
}
}, {
$project: {
activeUsers: true,
_id: false
}
}, {
$lookup: {
from: 'users',
'let': {
active: '$activeUsers'
},
pipeline: [{
$match: {
deactivated: false,
// The rest of the query works fine but here I would like to
// select only the elements that *aren't* inside
// the array (instead of the ones that *are* inside)
// but if I use '$nin' here mongoDB throws
// an 'unrecognized' error
$expr: {
$in: [
'$_id',
'$$active'
]
}
}
},
{
$project: {
_id: 1
}
}
],
as: 'users'
}
}]
Thanks
For negative condition use $not before $in operator,
{ $expr: { $not: { $in: ['$_id', '$$active'] } } }
I have two collections "Employee", "Office"
I am trying to find how many employees are in each area which contains office code. But there might be more than one office in the same area.
This is how my Office documents might look like
[
{
_id: "5b7d0f77e231b6b530b0ee5a",
code: "OB123456",
city: "Canmore"
// some other fields
},
{
_id: "5b7d0f77e531b6b530b0ee5b",
code: "OB858758",
city: "Vancouver"
},
{
_id: "5b7d0f77e531b6b530b0ee5d",
code: "EE858758",
city: "Vancouver"
},
]
this is how my Employee documents might look like
[
{
_id: "5b7d0f77e531b6b530b0edda",
name: 'Charlie',
office: {
code: 'OB123456'
// some other fields
}
},
{
_id: "5b7d0f73e531b6b530b0ee5b",
name: 'Bill',
office: {
code: 'EE858758'
}
},
{
_id: "5b7d0f77e531b6b530b0ee5n",
name: 'Echo',
office: {
code: 'OB123456'
}
},
];
I am looking into mongoose aggregate, and only tried
await Employee.aggregate([
{
$lookup: {
from: 'offices',
localField: 'office.code',
foreignField: 'code',
as: 'officeCode'
},
$group: {
_id: 'officeCode.city',
count: { $sum: 1 }
}
}
]);
which for sure does not work, I tried reading some of the aggregation documention but cannot come up with a good idea how to get this done
Thanks in advance for any suggestions or advices.
Sample output of what I am looking for
{
"Vancouver": 1,
"Canmore": 2
}
You have to start from office instead of employee, so you can create a list of code for each area (city), then lookup to map with your employees.
db.office.aggregate([
{
$group: {
_id: "$city",
codes: {
$addToSet: "$code"
}
}
},
{
$lookup: {
from: "employee",
localField: "codes",
foreignField: "office.code",
as: "employees"
},
},
{
$group: {
_id: null,
data: {
$push: {
k: "$_id",
v: {
$size: "$employees"
}
}
}
}
},
{
$replaceRoot: {
newRoot: {
"$arrayToObject": "$data"
}
}
}
])
The two last stages are here only to format your result as described in your expected output.
You can test it here
I need to count records grouped by tags and have filtered bofore including in ones
// in db
{tags: ['video', 'Alex'], ... },
{tags: ['video', 'John'], ... },
{tags: ['video', 'John'], ... },
{tags: ['text', 'Alex'], ... },
{tags: ['text', 'John'], ... },
client.db('mydb').collection('Files').aggregate(
[
{ $group: { _id: { tags: '$tags' }, total: { $sum: 1 } } },
{ $match: { tags: 'video' } },
],
).toArray()
But sadly I got zero docs. If remove $group section I got 3 docs.
In original request I anticipated 2 docs
{ _id: ['video', 'Alex'], total: 1 },
{ _id: ['video', 'John'], total: 2 }
In aggregation the order of pipeline is important, as output of previous stage is fed to the next one.
Your query is almost there basis the expected output. Just move $match stage before the $group stage.
Query:
db.collection.aggregate([
{
$match: {
"tags": "video"
}
},
{
$group: {
_id: {
tags: "$tags"
},
total: {
$sum: 1
}
}
}
]);
Working Example
I have a user document, each user has an array of objects
Given an array of item tags, I need to find the user whose item array has the item-tag, and return the entire user object except the items array, in which I only want to return the first item tags that existed in the tagArray that was used for the intial query.
//user document
{
user: 'John',
items: [ObjectId('ABC'), ObjectId('123') ...]
}
//item document
{
_id: ObjectId('ABC'),
tag: 'some-unique-id'
},
{
_id: ObjectId('DEF'),
tag: 'some-unique-tag'
}
Users have a 1-to-N relationship with items, the items may repeat within the User's items array.
This is what I current have, which returns the entire user object, but also all the items within the array.
const tagArray = [ 'some-unique-id', 'some-unique-tag']
items.aggregate([
{ $match: { 'tag': { $in: tagArray } }},
{ $lookup: {
from: "users",
localField: "tag",
foreignField: '_id',
as: 'userInfo'
}
},
{
$project: {??} //<--- I'm pretty sure I'm missing something in the project
])
Outcome that I have now:
{
_id: ObjectId('ABC'),
tag: 'some-unique-id'
userInfo : [ {user: 'John', items: [ObjectId('ABC'), ObjectId('123') ...] }]
}
What I want to achieve:
{
_id: ObjectId('ABC'),
tag: 'some-unique-id'
userInfo : [ {user: 'John', items: [ObjectId('ABC')]} ]
}
Edit:
There is a similar question here : Retrieve only the queried element in an object array in MongoDB collection
However in my case, I need the filter condition to be "one of the the tags that is in the tagArray.
Any suggestion or pointers would be appreciated, thank you!
I don't know if I understood well what you need, but I think this is a good start (maybe you can modify it by yourself):
Test data:
// users collection
[
{
user: "John",
items: [
ObjectId("5a934e000102030405000002"),
ObjectId("5a934e000102030405000003")
]
}
]
// items collection
[
{
_id: ObjectId("5a934e000102030405000002"),
tag: "some-unique-id"
},
{
_id: ObjectId("5a934e000102030405000009"),
tag: "some-unique-tag"
}
]
}
Query:
db.users.aggregate([
{
$lookup: {
from: "items",
localField: "items",
foreignField: "_id",
as: "userInfo"
}
},
// create new fields inside the userInfo array
{
$project: {
"userInfo.user": "$user",
"userInfo.items": "$items",
"tag": {
$arrayElemAt: ["$userInfo.tag", 0]
}
}
},
// filter the userInfo.items field, based on _id field
// it's important to use $arrayElemAt here
{
$addFields: {
"userInfo.items": {
$filter: {
input: {
$arrayElemAt: [
"$userInfo.items",
0
]
},
as: "i",
cond: {
$in: [
"$$i",
[
"$_id"
]
]
}
}
}
}
}
])
Result:
[
{
"_id": ObjectId("5a934e000102030405000002"),
"tag": "some-unique-id",
"userInfo": [
{
"items": [
ObjectId("5a934e000102030405000002")
],
"user": "John"
}
]
}
]