Mongodb, find entries from name or categories from a list of defined categories - mongodb

Using mongodb, I am trying to find the entries where the name matches the user input among a definite list of categories OR where the categories from the same list match the user input.
I want to filter the collection to only show these categories ["major olympian", "twelve titan", "primordial deity"]
The user can search for a name from one of these categories or can search through the categories to display all the entries from the categories that matched the user input and are mentioned in the array
Before the collection was filtered this worked but now I only want the results from the filtered array of categories:
let _query = {
'$or': [
{
"name": {
"$regex": search,
"$options": "i"
}
},
{
"category": {
"$regex": search,
"$options": "i"
}
}
]
};
Here is a concrete example:
If the user type an it will return all entries where name contains an from categories ["major olympian", "twelve titan", "primordial deity"] as well as all the entries from major olympian and twelve titan
Here is a sample of my collection with one category creature that is never displayed :
{
"name": "Zeus",
"greekName": "Ζεύς, Zeus",
"romanName": "Jupiter",
"description": "King of the gods, ruler of Mount Olympus, and god of the sky, weather, thunder, lightning, law, order, and justice. He is the youngest son of Cronus and Rhea. He overthrew Cronus and gained the sovereignty of heaven for himself. In art he is depicted as a regal, mature man with a sturdy figure and dark beard. His usual attributes are the royal scepter and the lightning bolt. His sacred animals include the eagle and the bull. His Roman counterpart is Jupiter, also known as Jove.",
"category": "major olympian"
},
{
"name": "Ophiogenean dragon",
"greekName": "",
"romanName": "",
"description": "a dragon that guarded Artemis' sacred grove in Mysia.",
"category": "creature",
},
{
"greekName": "Ἀχλύς (Akhlýs)",
"name": "Achlys",
"description": "The goddess of poisons and the \"Death-Mist\", and personification of misery and sadness. Said to have existed before Chaos itself.",
"category": "primordial deity",
"romanName": ""
}

You can try something like this.
const _query = {
category: {
$in: TEMP_CATEGORIES
},
'$or': [{
"name": {
"$regex": search,
"$options": "i"
}
}, {
"category": {
"$regex": search,
"$options": "i"
}
}]
};

Thanks to #Veeram comment I managed to make it work.
const TEMP_CATEGORIES = ["major olympian", "twelve titan", "primordial deity"];
const _query = {
'$or': [
{
$and: [
{
"name": {
"$regex": search,
"$options": "i"
}
},
{
category: { $in: TEMP_CATEGORIES }
}
],
},
{
$and: [
{
"category": {
"$regex": search,
"$options": "i"
}
},
{
category: { $in: TEMP_CATEGORIES }
}
],
},
]
};
Greek
.find(_query)
.sort({ name: 1 })
.exec((err, greeks) => {
if (err) {
console.warn(err);
reject(err)
} else {
resolve(greeks);
}
});
});
I don't know if it is the proper way but, it appears to work.(I guess I should unit test it...)

Related

Populate Search MongoDB Objects

Can you please tell me how to search for a nested object (only _id appears in it and is expanded via .populate() )?
I need to search by title and album.title, how can I do this?
async getAll(searchTerm?: string) {
let options = {}
if (searchTerm) {
options = {
$or: [
{
title: new RegExp(searchTerm.trim(), 'gi'),
},
],
}
}
return this.TrackModel.find(options)
.select('-updatedAt -__v')
.sort({ createdAt: 'desc' })
.populate('album author')
.exec()
}
image of JSON below
[
{
"_id": "638cec330c055283f3eeb227",
"poster": "/uploads/tracks/Coolio/Coolio-cover.jpg",
"title": "Gangsta's Paradise",
"slug": "gangstasparadise",
"duration": 240,
"countPlays": 7269322,
"trackUrl": "/uploads/tracks/Coolio/Coolio - Gangstas Paradise.mp3",
"album": [
{
"_id": "638ceb960c055283f3eeb225",
"title": "Gangsta's Paradise",
"slug": "gangstasparadise",
"poster": "/uploads/tracks/Coolio/Coolio-cover.jpg",
"author": [
"638ce9f50c055283f3eeb223"
],
"createdAt": "2022-12-04T18:48:54.306Z",
"updatedAt": "2022-12-04T18:48:54.306Z",
"_v": 0
}
]
}
]
I tried to search using $or, but I ran into a problem that at the time of the search, only _id is stored in the album array.

MongoDB: find if ID exist in array of objects

I was wondering, is there a way in MongoDB to get all the documents from one collection excluding those that exists in another, without using aggregation.
Let me explain myself more;
I have a collection of "Users", and another collection where there is an array of objects.
Something like this:
User
}
"_id": "61e6bbe49d7efc57f895ab50",
"name": "User 1"
},
{
"_id": "61e6b9239d7efc57f895ab02",
"name": "User 2"
},
{
"_id": "61cae6176d0d9a36efd8f190",
"name": "User 3"
},
{
"_id": "61cade886d0d9a36efd8f11a",
"name": "User 4"
},
The other collection looks like this:
{
users: [
{
user: {
"_id": "61e6b9239d7efc57f895ab02",
"name": "User 2",
},
...
},
{
user: {
"_id": "61cae6176d0d9a36efd8f190",
"name": "User 3",
},
...
},
],
},
I would like to get all the users in "array 1" excluding those in "array 2".
So the result should be:
[
{
"_id": "61e6b9239d7efc57f895ab02",
"name": "User 1"
},
{
"_id": "61cae6176d0d9a36efd8f190",
"name": "User 4"
},
],
So, is there a way to do that without the need to do aggregation.
you can use the $and like this:
const found = await User.find({
$and: [
{ _id: { $in: array1 } },
{ _id: { $nin: array2 } }
]
});
For anyone looking for a solution, if you want to add new element to an array of objects, where you, somehow, forgot to initialize the _id field, remember you have to, because mongo adds that field automatically (to use it for filtering/indexation).
All you have to do, is to write something like this;
const data = Data.create({
_id: mongoose.Types.ObjectId.createFromHexString(uid),
users: [{ user: userId, initiator: true, _id: userId}],
})
and for the searching, you write what you always write;
const found = await User.find({ _id: { $nin: data.users } });

Atlas Search works too slow when using facet

I have a big collection (over 22M records, approx. 25GB) on an M10 cluster with MongoDB version 4.4.10. I set up an Atlas search index on one field (address) and it works pretty fast when I request through the search tester. However, when I try to paginate it by specifying a facet, it gets extremely slow in comparison with the query without the facet. Is there a way to optimize the facet or somehow replace the facet with one that works faster ? Below are the plain query and another one with the facet:
db.getCollection("users").aggregate([{
$search: {
index: 'address',
text: {
query: '7148 BIG WOODS DR',
path: {
'wildcard': '*'
}
}
}
}]);
db.getCollection("users").aggregate([{
$search: {
index: 'address',
text: {
query: '7148 BIG WOODS DR',
path: {
'wildcard': '*'
}
}
}
}, {
$facet: {
paginatedResult: [
{
$limit: 50
},
{
$skip: 0
}
],
totalCount: [
{
$count: 'total'
}
]
}
}]);
The fast and recommend way is using facet with the $searchMeta stage to retrieve metadata results only for the query
"$searchMeta": {
"index":"search_index_with_facet_fields",
"facet":{
"operator":{
"compound":{
"must":[
{
"text":{
"query":"red shirt",
"path":{
"wildcard":"*"
}
}
},
{
"compound":{
"filter":[
{
"text":{
"query":["clothes"],
"path":"category"
}
},
{
"text":{
"query":[
"maroon",
"blackandred",
"blackred",
"crimson",
"burgandy",
"burgundy"
],
"path":"color"
}
}
]
}
}
]
}
},
"facets":{
"brand":{
"type":"string",
"path":"brand"
},
"size":{
"type":"string",
"path":"size"
},
"color":{
"type":"string",
"path":"color"
}
}
}
}
}
Here we are fetching 3 facets brand, size, and color, which we need to be defined in your search_index as Facet fields such as
{
"mappings": {
"dynamic": false,
"fields": {
"category": [
{
"type": "string"
}
],
"brand": [
{
"type": "string"
},
{
"type": "stringFacet"
}
],
"size": [
{
"type": "string"
},
{
"type": "stringFacet"
}
],
"color": [
{
"type": "string"
},
{
"type": "stringFacet"
}
]
}
}
}
category is defined only as string since we are not using it in facets but only as a filter field.
We can also replace filter op with must or should based on our requirement.
Finally, we will get as our result.
*p.s. I am also new to mongo and got to this solution after searching a lot, so please upvote if you find it useful, also let me know if there is any error/improvement you notice. Thanks *

Update recursive document in MongoDB

I have a recursive structure in one of my MongoDB collections, like so:
{
"id": String,
"date": Date,
"text": String
"replies": [{
// Same structure as above
}]
}
I need to update a document in this hierarchy by adding in a reply to a nested document. The guarantees are as follows:
The object to reply to is guaranteed to exist.
The path to the object to which a reply is to be posted is known (i.e., we have a sequence of the _id attributes to navigate).
The recursion depth is not bounded.
Searching SO gives me the following relevant questions:
Querying, 2013 - The recommendation was to switch to a graph DB.
Updating, 2015 - The recommendation was to use the $[] operator.
Based on the latter, my attempt was:
await commCollection.update(
{ _id: topLevel['_id'] },
{
$push: {
'replies.$[].$[comment_arr].replies': {
id: commentId,
text: comment,
date,
replies: []
}
}
},
{ arrayFilters: [{ 'comment_arr.id': responseTo }]}
);
where topLevel is the root document, responseTo is the id attribute of the object to add the reply to. This however, does not seem to work. What am I doing wrong, and how do I achieve this?
Update: An example below. Here's an example of a document from MongoDB Atlas:
{
"_id": {
"$oid": "605fdb8d933c5f50b4d2225e"
},
"id": "mr9pwc",
"username": "recrimination-logistical",
"upvotes": {
"$numberInt": "0"
},
"downvotes": {
"$numberInt": "0"
},
"text": "Top-level comment",
"date": {
"$date": {
"$numberLong": "1616894861300"
}
},
"replies": [{
"id": "dflu1h",
"username": "patrolman-hurt",
"upvotes": {
"$numberInt": "0"
},
"downvotes": {
"$numberInt": "0"
},
"text": "Testing reply level 1!",
"date": {
"$date": {
"$numberLong": "1618387567042"
}
},
"replies": [] // ----> want to add a reply here
}]
}
I've indicated where we want a reply added. responseTo in this case would be dflu1h.
Just need to remove $[] positional
Field name in arrayFilters must be an alphanumeric string beginning with a lowercase letter, Change comment_arr to any alphabets only like commentArr, remove special characters
await commCollection.update(
{ _id: topLevel['_id'] },
{
$push: {
"replies.$[commentArr].replies": {
id: commentId,
text: comment,
date,
replies: []
}
}
},
{ arrayFilters: [{ "commentArr.id": responseTo }] }
)
Playground

How to project sub-document values in an array using MongoDB?

I am creating an application that needs to support multiple languages in the content. I am using the following document structure; each field that needs translation is an object with each language and associated string.
{
"greeting": {
"en": "Hello",
"fr": "Bonjou"
},
"places": [
{
"name": {
"en": "CN Tower",
"fr": "La Tour CN"
}
},
{
"name": {
"en": "Skydome",
"fr": "Le Skydome"
}
}
]
}
I want to be able to aggregate the result into a single selected language to produce the following result.
{
"greeting": "Hello",
"places": [
{
"name": "CN Tower"
},
{
"name": "Skydome"
}
]
}
Mapping the greeting field was simple, requiring only a $greeting.en. However, sub-documents in an array are a lot trickier to perform the path syntax with. Instead, I've had to use a $map to achieve the result I needed.
db.collection.aggregate([{
$project: {
greeting: "$greeting.en",
places: {
$map: {
input: "$places",
as: "place",
in: { name: "$$place.name.en"
}
}
}
}
}])
Is there a way to achieve this without using $map and instead using the path syntax?