I was wondering if it was possible to destructure arrays while I am still in the MongoDB aggregation pipeline which would make my code alot neater.
For example, I have the following aggregation pipeline.
await User.aggregate([
{ $match: { _id: userID } },
{
$project: { chatLogs: 1, username: 1, profilePicURL: 1 },
},
{ $unwind: "$chatLogs" },
{
$lookup: {
from: "users",
let: { recipientID: "$chatLogs.recipientID" },
pipeline: [
{
$match: { $expr: { $eq: ["$_id", "$$recipientID"] } },
},
{ $project: { profilePicURL: 1 } },
],
as: "chatLogs.recipientID",
},
},
]);
This gives the following results when queried:
{
"_id": "5f2ffb54eea9c2180a732afa",
"username": "joe",
"profilePicURL": "/images/profile/default_profile.png",
"chatLogs": {
"recipientID": [
{
"_id": "5f2faf5ad18a76073729f475",
"profilePicURL": "/images/profile/default_profile.png"
}
],
"chat": "5f30b6c3d117441c2abda1ba"
}
}
In my case, because "recipientID" represents a default MongoDB id, it will always be unique. Hence I would prefer the following, where the resulting recipientID field is no longer a meaningless array
Desired results:
{
"_id": "5f2ffb54eea9c2180a732afa",
"username": "joe",
"profilePicURL": "/images/profile/default_profile.png",
"chatLogs": {
"recipientID": {
"_id": "5f2faf5ad18a76073729f475",
"profilePicURL": "/images/profile/default_profile.png"
}
"chat": "5f30b6c3d117441c2abda1ba"
}
}
You can deconstruct recipientID array using $unwind in last pipeline,
await User.aggregate([
... // your all pipelines
// add this line
{ $unwind: "$chatLogs.recipientID" }
]);
Related
I have a questions collection with _id and name and other fields(..), and a tests collection with _id, name and array of questions.
I'm trying to get all the questions with their fields and adding a field "usedIn" which counts the number of tests that the specific questions is present in.
questions = await Question.aggregate([
/*{
"$match": {
params
}
},*/
{
"$lookup": {
"from": "tests",
"let": {"questionId": "$_id"},
pipeline: [
{
"$match": {
"$expr": {
"$in": ["$$questionId", "$questions"],
},
},
},
],
as: "tests"
}
},
{
"$addFields": {
"usedIn": {
"$size": "tests"
}
}
},
{
"$project": fieldsObject
},
])
This code is giving me this error:
Error: Failed to optimize pipeline :: caused by :: The argument to $size must be an array, but was of type: string
What Am I doing wrong ?
You can do it like this:
db.questions.aggregate([
{
"$lookup": {
"from": "tests",
"localField": "_id",
"foreignField": "questions",
"as": "usedIn"
}
},
{
"$project": {
"usedIn": {
"$size": "$usedIn"
},
"name": 1
}
}
])
Working example
I am using mongodb aggregation with a collection named files
that has a relationship with another collection named file_upload.
files = {
type: String,
media: { type: Schema.Types.ObjectId, ref: 'file_upload', required: true },
}
file_upoad = {
name: String,
}
This is the query
const data = await strapi.query('files').model.aggregate([
{
$lookup: {
from: "analytics",
localField: "_id",
foreignField: "file_id",
as: "hits",
}
},
{ $unwind: '$hits' },
{ $group: { _id: "$_id", hitsCount: { $sum: 1 } } },
{ $sort: { hitsCount: -1 } },
{ $limit: 1 },
])
my goal is to retrieve the media as part of the result since it is a relationship field, at the moment I get this
[
{
"_id": "61fd74367b6ee77b89bae34d",
"hitsCount": 12
},
{
"_id": "61fd74367b6ee77b89sddfee",
"hitsCount": 8
}
]
expected result
[
{
"_id": "61fd74367b6ee77b89bae34d",
"hitsCount": 12,
"media": {
name:"name1"
}
},
{
"_id": "61fd74367b6ee77b89sddfee",
"hitsCount": 8,
"media": {
name:"name2"
}
}
]
I'm new on mongodb. so I try design the schema for my collection is like below
all the ObjectId is not real
stockIn documents
{
serial:"stk0001",
date:'2021-06-11',
productInTransation:[
{
_id:"60ae220b066b8d9861118cb1",
productId:"60ae220b066b8d9861118cb2"
qty:2
},
{
_id:"60ae220b066b8d9861118cb1",
productId:"60ae220b066b8d9861118cb1",
qty:2
}
]
}
and I have a products collection
[
{
_id:"60ae220b066b8d9861118cb5",
name:"sepatu"
},
{
_id:"60ae220b066b8d9861118cb4",
name:"sendal"
}
]
so what I expect from those documents is just like below
{
serial:"stk0001",
date:'2021-06-11',
productInTransation:[
{
_id:"60ae220b066b8d9861118cb1",
productId:"60ae220b066b8d9861118cb2"
qty:2,
product:
{
_id:"60ae220b066b8d9861118cb5",
name:"sepatu"
},
},
{
_id:"60ae220b066b8d9861118cb1",
productId:"60ae220b066b8d9861118cb1",
qty:2,
product:
{
_id:"60ae220b066b8d9861118cb4",
name:"sendal"
}
}
]
}
this collection is just simplified from the real case.
and the problem I don't know how to do a query on mongodb, so the output will same as the expected. thank's for any help
You can use $lookup
$unwind to deconstruct the array
$lookup to join collections
$ifNull to make sure this doesn't give any NPE when we take from first element from the joined array using $arrayElemAt
$group to reconstruct the array
Here is the code
db.stockIn.aggregate([
{ $unwind: "$productInTransation" },
{
"$lookup": {
"from": "products",
"localField": "productInTransation.productId",
"foreignField": "_id",
"as": "productInTransation.product"
}
},
{
"$addFields": {
"productInTransation.product": {
"$ifNull": [ { "$arrayElemAt": [ "$productInTransation.product", 0 ] }, [] ]
}
}
},
{
"$group": {
"_id": "$_id",
"date": { "$first": "$date" },
"serial": { "$first": "$serial" },
"productInTransation": { $push: "$productInTransation" }
}
}
])
Working Mongo playground
I have query like this, in which I try to find average of all ratings linked to specific entity. And then return avg rating as an additional field to entity model. Now I want to filter out only those responses in which public field is set to be true.
This is how my query looks like:-
try {
const reviews = await Entity.aggregate([
{
$lookup: {
from: 'reviews',
localField: '_id',
foreignField: 'entityId',
as: 'avgRating',
},
},
{
$addFields: {
avgRating: {
$avg: {
$map: {
input: '$avgRating',
in: '$$this.rating',
},
},
},
},
},
{
$project: {
admin: 0,
createdAt: 0,
updatedAt: 0,
},
},
]);
res.send(reviews);
} catch (e) {
res.status(500).send();
}
the query works fine and gives the following response
{
{...},
{
"_id": "182ehc02031nd013810wd",
"public": false,
"organizations": [
"icnq03d0-2qidc-cq2c"
],
"cities": [
"1234"
],
"name": "test 3",
"__v": 0,
"avgRating": 5
},
{...},
}
I want to add another condition that it should return only those responses in which public is set to true.
I tried to use $filterbut did not work.
How to do this?
public is a document-level field so you need $match instead of $filter:
{ $match: { public: true } }
Mongo Playground
You can also simplify the way you calculate the average:
{
$addFields: {
avgRating: { $avg: 'avgRating.rating' }
}
}
should work
I need to count the number of parking spaces _id: 5d752c544f4f1c0f1c93eb23, which has the false value in the excluded property, how can I mount the query?
So far, I've been able to select the number of parking spaces with the excluded: false property. But are selecting from all parking lots
Note that there are two documents symbolizing a parking lot, where each has an array, called parkingSpaces, to record parking space documents.
The first document has 2 vacancies not excluded, so with the property excluded: false and the second has only one vacancy, which is not excluded either.
{
"_id": "5d752c544f4f1c0f1c93eb23",
"name": "estacionamento um",
"parkingSpace": [
{
"_id": "5d752cf54f4f1c0f1c93eb26",
"name": "vg001",
"excluded": true
},
{
"_id": "5d752cf54f4f1c0f1c93eb27",
"name": "vg001",
"excluded": false
},
{
"_id": "5d75339bc411423a9c14ac52",
"name": "vg002",
"excluded": false
}
]
},
{
"_id": "5d7706b60d354b72388a38f4",
"name": "estacionamento dois",
"parkingSpace": [
{
"_id": "5d77078a5173bb63bc87b7ca",
"name": "vg004",
"excluded": false
}
]
}
I need to add the number of parking spaces _id: 5d752c544f4f1c0f1c93eb23, which has the value false in the excluded property.
In the end, I need to return the value 2, referring to _id: 5d752c544f4f1c0f1c93eb23 parking spaces, which have the value false in the excluded property.
So far, with the following query, I was able to select the vacancies with the excluded property with the false value, but it is selecting from all parking lots.
const registeredParkingSpaces = await Parking.aggregate([
{ $unwind: '$parkingSpace' },
{ $match: { 'parkingSpace.excluded': false } },
{
$group: {
_id: parking_id,
total: { $sum: 1 }
}
}
]);
returns:
{
"message": [
{
"_id": "5d752c544f4f1c0f1c93eb23",
"total": 3
}
]
}
But it needs to return:
{
"message": [
{
"_id": "5d752c544f4f1c0f1c93eb23",
"total": 2
}
]
}
In aggregation 1st you match with id then go for the next step you will get your desire count because at this moment you are considering entire document "parkingSpace". Below is the sample code I guess it will work for you
const ObjectId = require('mongoose').Types.ObjectId;
const registeredParkingSpaces = await Parking.aggregate([
{ $match: { _id: objectId(body.id) } }
{ $unwind: '$parkingSpace' },
{ $match: { 'parkingSpace.excluded': false } },
{
$group: {
_id: parking_id,
total: { $sum: 1 }
}
}
]);
This is the query through which you can get the count of total parking .
** UPDATED **
db.collection.aggregate([
{
$match: {
"parkingSpace.excluded": false,
}
},
{
$project: {
parkingSpace: 1
}
},
{
"$group": {
"_id": "$_id",
"count": {
"$sum": {
"$size": {
"$filter": {
"input": "$parkingSpace",
"as": "el",
"cond": {
"$eq": [
"$$el.excluded",
false
]
}
}
}
}
}
}
}
])
You can check the solution from this LINK
For more information about $sum : Visit the official official document
Through this you get your solution .