Crazy Horse
asked on
How to remove _id from aggregate $match & $group query
I am trying to display unique records grouped by the particular slug passed in.
My output in postman looks like this though:
My desired output would be more like:
I don't want that top level `_id` there with everything nested inside of it.
I tried using `$project` but then I just end up with an empty array.
My output in postman looks like this though:
"subcats": [
{
"_id": {
"subcategory": {
"_id": "5d2b42c47b454712f4db7c37",
"name": "shirts"
}
}
}
]
My desired output would be more like:
"subcats": [
{
"_id": "5d2b42c47b454712f4db7c37",
"name": "shirts"
}
]
An example of a product in the database: "_id": "5d39eff7a48e6e30ace831dc",
"name": "A colourful shirt",
"description": "A nice colourful t-shirt",
"category": {
"_id": "5d35faa67b19e32ab3dc91ec",
"name": "clothing",
"catSlug": "clothing"
},
"subcategory": {
"_id": "5d2b42c47b454712f4db7c37",
"name": "shirts",
"catSlug": "shirts"
},
"price": 19
}
I don't want that top level `_id` there with everything nested inside of it.
I tried using `$project` but then I just end up with an empty array.
const products = await Product.find({ "category.catSlug": catslug }).select({
name: 1,
description: 1,
price: 1,
category: 1
});
const subcats = await Product.aggregate([
{ $match: { "category.catSlug": catslug } },
{ $group: { _id: { subcategory: "$subcategory" } } }
{ $project: { _id: 0, name: 1 } }
]);
Promise.all([products, subcats]);
res.status(200).json({
products,
subcats
});
ASKER
Hi Julian, thanks for your response. I am glad you picked up the first issue because I want to do it right. Could you please tell me the best way to do it?
What I am trying to do is query the database and get a list of products in one query and then a list of subcategories as well which I have split into 2 queries. The results are based on the category name in the slug ie: clothing.
The list of subcategories is going to end up being a filter where a user clicks on a subcategory name and it filters products shown by that subcategory.
What I am trying to do is query the database and get a list of products in one query and then a list of subcategories as well which I have split into 2 queries. The results are based on the category name in the slug ie: clothing.
The list of subcategories is going to end up being a filter where a user clicks on a subcategory name and it filters products shown by that subcategory.
Just remove the await from the queries - and pass the promises to Promise.All.
As for the query - I am still not clear - your first snippet showed this
As for the query - I am still not clear - your first snippet showed this
"subcats": [
{
"_id": {
"subcategory": {
"_id": "5d2b42c47b454712f4db7c37",
"name": "shirts"
}
}
}
]
Which does not seem to match up with the values that are being returned by your query?
ASKER
If I remove await then I probably have to remove async as well and end up with this:
But then I get an error
TypeError Converting circular structure to JSON
--> starting at object with constructor 'Server'
| property 's' -> object with constructor 'Object'
| property 'coreTopology' -> object with constructor 'Server'
| ...
| property 's' -> object with constructor
My problem with the returned query is the unnecessary "_id". I don't want it in there. I highlighted it below.
"subcats": [
{
"_id": {
"subcategory": {
"_id": "5d2b42c47b454712f4db7c37" ,
"name": "shirts"
}
}
}
]
exports.getCatSlug = (req, res) => {
const catslug = req.params.catslug;
const products = Product.find({ "category.catSlug": catslug }).select({
name: 1,
description: 1,
price: 1,
category: 1
});
const subcats = Product.aggregate([
{ $match: { "category.catSlug": catslug } },
{ $group: { _id: { subcategory: "$subcategory.name" } } }
]);
Promise.all([products, subcats]);
res.status(200).json({
products,
subcats
});
But then I get an error
TypeError Converting circular structure to JSON
--> starting at object with constructor 'Server'
| property 's' -> object with constructor 'Object'
| property 'coreTopology' -> object with constructor 'Server'
| ...
| property 's' -> object with constructor
My problem with the returned query is the unnecessary "_id". I don't want it in there. I highlighted it below.
"subcats": [
{
"_id": {
"subcategory": {
"_id": "5d2b42c47b454712f4db7c37"
"name": "shirts"
}
}
}
]
ASKER
So, this is where I am now. I kept async/await and just didn't use 'Promise.all'. Is that okay?
I am happy with the products array I get back but the subcats array now looks like this:
This is better because for now I just wanted the names which I am getting, but $group is giving me _id but I would rather it said "name". Is the only way around this to create a new array?
exports.getCatSlug = async (req, res) => {
const catslug = req.params.catslug;
const products = await Product.find({ "category.catSlug": catslug }).select({
name: 1,
description: 1,
price: 1,
category: 1
});
const subcats = await Product.aggregate([
{ $match: { "category.catSlug": catslug } },
{
$group: {
_id: "$subcategory.name"
}
}
]);
res.status(200).json({
products,
subcats
});
I am happy with the products array I get back but the subcats array now looks like this:
"subcats": [
{
"_id": "shirts"
},
{
"_id": "shoes"
},
{
"_id": "ties"
}
]
This is better because for now I just wanted the names which I am getting, but $group is giving me _id but I would rather it said "name". Is the only way around this to create a new array?
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Thanks Julian, I have been using async await so much that I forgot that I need to use then and catch blocks when not using async await! Besides the map part, your code gives me the same result as to what I had changed it to (not sure if you saw that post of mine but perhaps one of your comments was related to that code? So, I just wanted to find out if you were saying this was also bad practice or just the wrong way I had done the Promise.all which totally makes sense now.
exports.getCatSlug = async (req, res) => {
const catslug = req.params.catslug;
const products = await Product.find({ "category.catSlug": catslug }).select({
name: 1,
description: 1,
price: 1,
category: 1
});
const subcats = await Product.aggregate([
{ $match: { "category.catSlug": catslug } },
{
$group: {
_id: "$subcategory.name"
}
}
]);
res.status(200).json({
products,
subcats
});
The map part is what reduced the subcategory return from
"subcats": [
{
"_id": {
"subcategory": {
"_id": "5d2b42c47b454712f4db7c37",
"name": "shirts"
}
}
}
]
To"subcats": [
{
"_id": "5d2b42c47b454712f4db7c37",
"name": "shirts"
}
]
Besides the map part, your code gives me the same result as to what I had changed it to (not sure if you saw that post of mine but perhaps one of your comments was related to that code?Your post was still using async / await. Mine used a Promise.all().then() approach with a map to return an altered subcats array.
I see you changed your query structure - which I assume is now returning the correct results.
I am not familiar with MongoDB query syntax so was not sure if your changed query was returning the data in the form that you wanted.
In event that it was not - the map code was to demonstrate how to modify an array to a different structure.
I am not familiar with MongoDB query syntax so was not sure if your changed query was returning the data in the form that you wanted.
In event that it was not - the map code was to demonstrate how to modify an array to a different structure.
ASKER
Your post was still using async / await. Mine used a Promise.all().then() approach with a map to return an altered subcats array.
Just wanted to get some clarity on the modified code below and asking if the async/await version below is still incorrect or if it is okay as I couldn't quite make out from your initial answer if it was still wrong since I no longer used Promise.all as you said:
The await solution will work but it is not optimal and not considered good practice
exports.getCatSlug = async (req, res) => {
const catslug = req.params.catslug;
const products = await Product.find({ "category.catSlug": catslug }).select({
name: 1,
description: 1,
price: 1,
category: 1
});
const subcats = await Product.aggregate([
{ $match: { "category.catSlug": catslug } },
{
$group: {
_id: "$subcategory.name"
}
}
]);
res.status(200).json({
products,
subcats
});
If the above isn't good practice or not a good way of doing it then I will use Promise.all going forward as per your code but was just wanted to make sure I understood what you were saying properly.
Yes, with the await as you have it you have turned your async calls into synch calls - so you don't need Promise.all as the promises will resolve before continuing.
ASKER
Great, thanks for all your help, it is much appreciated!
You are welcome.
await is going to wait for the promise to resolve on each call so you are turning what should be a parallel operation (handled by Promise.All) into a synchronous one.
Other than that I am trying to reconcile your first listing with the others.
What is it you are wanting to do - it is not clear.