Algolia retrieve results by multiple facets - algolia

First of all, I am using Algolia JavaScript API Client V3 (Deprecated)
I have the following records
{
category: SEDAN,
manufacturer: Volkswagen,
id: '123'
},
{
category: COUPE,
manufacturer: Renault,
id: '234'
},
{
category: SEDAN,
manufacturer: Fiat,
id: '345'
},
{
category: COUPE,
manufacturer: Peugeot,
id: '456'
},
{
category: SUV,
manufacturer: Volkswagen,
id: '567'
}
I want to query Algolia and get something similar to the following json
{
categories: {
SEDAN: {
count: 2
items: [{
Volkswagen: {
count 1,
items: [{
id: '123'
}]
}
},
{
Fiat: {
count 1,
items: [{
id: '345'
}]
}
}]
},
COUPE: {
count: 2
items: [{
Renault: {
count 1,
items: [{
id: '234'
}]
}
},
{
Peugeot: {
count 1,
items: [{
id: '456'
}]
}
}]
},
SUV: {
count: 1,
items: [{
Volkswagen: {
count 1,
items: [{
id: '567'
}]
}
}]
}
}
}
I have been trying to query Algolia
index
.search({
query: '',
facets: ['category', 'manufacturer'],
attributesToRetrieve: []
})
.then((result) => {
console.log(result.facets);
});
But I am not sure if it is possible to combine the facets

facets added to a query doesn't work that way. It will simply return the record count for each facet value, not the actual records (https://www.algolia.com/doc/api-reference/api-parameters/facets/)
You can create filters around facets and use those to display results by facet value, but there isn't a way to build a single response JSON that is already grouped by facets like you show above. https://www.algolia.com/doc/api-reference/api-parameters/filters/

Related

Get only matched array object along with parent fields

I also checked the following question and tried various other things but
couldn't get it working
Retrieve only the queried element in an object array in MongoDB collection
I have the following document sample
{
_id: ObjectId("634b08f7eb5cb6af473e3ab2"),
name: 'India',
iso_code: 'IN',
states: [
{
name: 'Karnataka',
cities: [
{
name: 'Hubli Tabibland',
pincode: 580020,
location: { type: 'point', coordinates: [Array] }
},
{
name: 'Hubli Vinobanagar',
pincode: 580020,
location: { type: 'point', coordinates: [Array] }
},
{
name: 'Hubli Bengeri',
pincode: 580023,
location: { type: 'point', coordinates: [Array] }
},
{
name: 'Kusugal',
pincode: 580023,
location: { type: 'point', coordinates: [Array] }
}
]
}
]
}
I need only the following
{
_id: ObjectId("634b08f7eb5cb6af473e3ab2"),
name: 'India',
iso_code: 'IN',
states: [
{
name: 'Karnataka',
cities: [
{
name: 'Kusugal',
pincode: 580023,
location: { type: 'point', coordinates: [Array] }
}
]
}
]
}
Following is the query that I have tried so far but it returns all the cities
db.countries.find(
{
'states.cities': {
$elemMatch: {
'name' : 'Kusugal'
}
}
},
{
'_id': 1,
'name': 1,
'states.name': 1,
'states.cities.$' : 1
}
);
I was able to achieve it with the help of aggregation.
db.countries.aggregate([
{ $match: { "states.cities.name": /Kusugal/ } },
{ $unwind: "$states" },
{ $unwind: "$states.cities" },
{ $match: { "states.cities.name": /Kusugal/ } }
]);
1st line $match will query the records with cities with only Kusugal
2nd & 3rd line $unwind will create a separate specific collection of documents from the filtered records
3rd line $match will filter these records again based on the condition
In simple aggregation processes commands and sends to next command and returns as an single result.

Mongoose - projection with $elemMatch on nested fields

I'm relatively new to MongoDB/Mongoose and I've only performed simple queries. Now I'm having some trouble trying to filter my database in a slightly more complex way. I already did some research to tackle my previous issues, but now I can't move forward. Here's what happening:
This is my schema:
const userSchema = new mongoose.Schema({
email: String,
password: String,
movies: [
{
title: String,
movieId: Number,
view_count: Number,
rating: Number,
review: String,
},
],
lists: {
watched_movies: [
{
title: String,
director: String,
genres: [{ type: String }],
runtime: Number,
date: Date,
},
],
},
});
I want to make a GET request that matches simultaneously "lists.watched_movies": { _id: req.params.entryId } and also "movies.title": req.body.title for a given email, so that the outcome of the findOne query would be just those elements and not the whole document. What I'm trying to accomplish is something like that:
{
email: "some.email#gmail.com",
movies: [
{
title: "Mongoose Strikes Back",
movieId: 123,
view_count: 1,
rating: 3,
review: "Very confusing movie!"
}
],
lists: {
watched_movies: [
{
_id: 4321
title: "Mongoose Strikes Back",
director: "Mongo",
genres: ["Drama"],
runtime: 150,
date: "2021-11-22"
}
]
}
}
My first attempt to tackle it, however, wasn't successful. Here's what I tried:
router.route("/:entryId").get((req, res) => {
User.findOne(
{ email: "some.email#gmail.com" },
{
"lists.watched_movies": { $elemMatch: { _id: req.params.entryId } },
movies: { $elemMatch: { title: req.body.title } },
},
(err, entry) => {
if (!err) {
res.send(entry);
console.log(entry);
} else {
console.log(err);
}
}
);
});
It says that Cannot use $elemMatch projection on a nested field. I thought that maybe I can solve it by changing my schema, but I'd like to avoid it if possible.
For your scenario, you can use $filter to filter document(s) in nested array field.
db.collection.find({
email: "some.email#gmail.com"
},
{
"lists.watched_movies": {
"$filter": {
"input": "$lists.watched_movies",
"cond": {
"$eq": [
"$$this._id",
4321// req.params.entryId
]
}
}
},
movies: {
$elemMatch: {
title: "Mongoose Strikes Back"// req.body.title
}
}
})
Sample Mongo Playground

Aggregate data and populate in one request

I am a bit puzzled by populate in MongoDB.
I've got a Schema:
import { Schema, Document, model } from "mongoose";
export interface ProductGroupType {
id: Schema.Types.ObjectId,
title: String,
name: String,
description: String,
}
const ProductGroupSchema: Schema<Document<ProductGroupType>> = new Schema({
title: { type: String, trim: true },
name: { type: String, trim: true },
description: { type: String, trim: true },
}, { collection: "productGroups", timestamps: true });
export const ProductGroupModel = model('ProductGroup', ProductGroupSchema);
and products
import { Schema, Document, model } from "mongoose";
import { plugin as autocomplete } from 'mongoose-auto-increment';
const ProductSchema: Schema<Document<IProduct>> = new Schema({
article: Number,
name: String,
category: { type: Schema.Types.ObjectId, ref: 'ProductCategory' },
group: { type: Schema.Types.ObjectId, ref: 'ProductGroup' },
price: { type: Number, default: 0 },
discount: { type: Number, default: 0 },
stock: {
available: { type: Number, default: 0 },
reserved: { type: Number, default: 0 },
},
images: [Object],
description: String,
productDetails: Object,
}, { collection: "products", timestamps: true });
ProductSchema.plugin(autocomplete, {
model: 'Product',
field: 'article',
startAt: 10000,
});
export const ProductModel = model('Product', ProductSchema);
I need to make a request and group on the MongoDB side data by the field 'group'.
I can make this like this:
await ProductModel.aggregate([
{ $match: { category: Types.ObjectId(queryCategory.id) } },
{
$group: {
_id: '$group',
products: {
$push: {
id: '$_id',
name: '$name',
article: '$article',
price: '$price',
discount: '$discount',
description: '$description',
group: '$groupName',
}
},
count: { $sum: 1 },
}
},
]);
but the output here is:
[
{ _id: 61969583ad32e113f87d0e99, products: [ [Object] ], count: 1 },
{
_id: 61993fff452631090bfff750,
products: [ [Object], [Object] ],
count: 2
}
]
almost what I need but I've been playing around with population and I cannot make it work with Aggregation framework.
I already tried to use the 'lookup' operator but it returns an empty array and doesn't want to work.
That's how I wanted to make it work:
const products: Array<IProduct> = await ProductModel.aggregate([
{ $match: { category: Types.ObjectId(queryCategory.id) } },
{
$group: {
_id: '$group',
products: {
$push: {
id: '$_id',
name: '$name',
article: '$article',
price: '$price',
discount: '$discount',
description: '$description',
group: '$groupName',
}
},
count: { $sum: 1 },
}
},
{
$lookup: {
"from": "productGroups",
"localField": "group",
"foreignField": "_id",
"as": "groupName"
},
},
]);
Is it possible to get the same result as I've got now but populate in the same query group field?
So far the only way I've managed to populate it like this as the second request:
await ProductGroupModel.populate( products.map( (product: any) => {
return {
_id: new ProductGroupModel(product),
products: product.products,
count: product.count,
}
} ), { "path": "_id" } )
In a MongoDB aggregation pipeline, the $group stage passes along only those field explicitly declared in the stage.
In the same pipeline you show, the documents passed along by the $group stage would contain the fields:
_id
products
count
When the exector arrives a the $lookup stage, none of the documents contain a field named group.
However, the value previously contained in the group field still exists, in the _id field.
In the $lookup stage, use
"localField": "_id",
to find documents based on that value.

How to pull array of documents from array of documents?

I have a document like this
{
users: [
{
name: 'John',
id: 1
},
{
name: 'Mark',
id: 2
},
{
name: 'Mike',
id: 3
},
{
name: 'Anna',
id: 4
}
]
}
and I want to remove users from the array with ids 2 and 4. To do that I execute the following code:
const documents = [
{
id: 2
},
{
id: 4
},
]
Model.updateOne({ document_id: 1 }, { $pull: { users: { $in: documents } } });
But it doesn't remove any user.
Could you say me what I'm doing wrong and how to achieve the needed result?
This works if you can redefine the structure of your documents array:
const documents = [2, 4]
Model.updateOne({ document_id: 1 }, { $pull: { users: { id: { $in: documents } } } })

When I need to run mapReduce in MongoDB

I'm newbie with MongoDB.
I have created a mapReduce on my Person collection to group cities.
db.Person.find()
[{
name: 'Bob',
addresses: [
{
street: 'Vegas Street',
neighborhood: {
name: 'Center',
city: {
name: 'Springfield'
}
}
}, {
.....
}
]
}, {
....
}]
And this is my mapReduce:
db.Person.mapReduce(function() {
for (var i = 0; i < this.address.length-1; i++) {
var address = this.address[i];
emit(address.neighborhood.city.name, 1);
}
}, function(k, v) {
return v.length;
}, { out: 'City' });
Then I use this to list my cities:
db.City.find().sort({'_id:', 1})
[{
_id: 'Springfield',
value: 3
}, {
_id: 'City B',
value: 2
}, {
...
}]
My question is about the City data, I need run the mapReduce each time I insert, update or delete on my Personcollection or it runs automatically?