MongoDB agregation with filtering in array - javascript

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

Related

mongoDB - find by id after aggregation

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}`)] }
}
}
}
}
])

Use mongoDB $lookup to find documents in another collection not present inside an array

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'] } } }

how to use mongoose aggregation to get sum of two matching documents depending on field

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

MongoDB: Project only the items that was queried for in the array?

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"
}
]
}
]

MongoDB Aggregation: calculation for every unique/distinct value

I got this data set from collection
{
item: 124001
price: 6
},
{
item: 124001
price: 6
},
{
item: 124121
price: 16
},
{
item: 124121
price: 13
},
{
item:n
price: x
}
from code:
let INDX = [xxx,xxx,xxx,xxx, ..n]
auctions.aggregate([
{
$match: { item: { $in: INDX }}
}
The problem is right after it, in the $group stage. For example I'd like to receive $min, $max or $avg 'price' for every unique/distinct item.
When I'm trying to use:
{
$group: {
min_1: { $min: "$price",}
}
}
I receive just $min from all data,
[ { _id: 0, min_1: 0 } ]
but I need something like:
{ _id: 124119, min_1: 66500 },
{ _id: 124437, min_1: 26398 }
Ok, here is a simple answer:
Just don't forget about _id field and use it, at $group stage, just like:
{
$group: {
_id: "$item",
min_1: {
$min: '$price',
}
}
}

Categories

Resources