Filter MongoDB autocomplete - mongodb

When building an autocomplete aggregation pipeline using MongoDb Atlas search indexes. How do I limit the autocomplete to only search through specific ID's?
I'm building search functionality where a user can search for people and the application should autocomplete the search but the user should only be suggested users that it is allowed to view.
My pipeline (works but need to be filtered):
{
'compound': {
'should': [{
'autocomplete': {
'query': "John",
'path': 'firstName'
}
}, {
'autocomplete': {
'query': "Doe",
'path': 'lastName'
}
},
]
}
}
If I have an array of people Id:s that the user can view, how do I go about only applying the autocomplete search on the people with the ID:s I supply?
Something like {_id: {$in myIdList}}

You can use the equals operator to match on an ObjectID. Specifically, in the must clause of your current Compound query, nest another Compound query which has 1 should clause per ObjectID you would like to filter for. Each should clause would be defined as an equals operator for a specific ObjectID. I can provide an example of how it would look like if needed. Also, just a reminder that the field that contains an array of ObjectID's needs to be indexed in Atlas Search so you can use the equals operator on it.

I would consider adding a filter clause to your compound query like so:
{
'compound': {
'filter': {
text': {
'query': userId,
'path': 'userId'
}
}
'should': [{
'autocomplete': {
'query': "John",
'path': 'firstName'
}
}, {
'autocomplete': {
'query': "Doe",
'path': 'lastName'
}
},
]
}
}
There's a great example here.

Related

Upserting nested fields with a dot(.) in key in MongoDB

I have query in MongoDB for which I'm trying to upsert an inner nested attribute that contains a dot(.) in the key. E.g. a document might look something like: (below is a fictitious example just to highlight the constraint I'm facing.)
const person = {
name: 'Peter',
address: {
'NY.postalCode': 12345,
'CA.postalCode': 23456,
}
}
However, when I try to update one of the nested attribute in address with the below $set operation, I get an additional object NY under address and its subKey postalCode as a result, instead of the flattened attribute within address.
await Person.findByIdAndUpdate(id, {
$set: {
'address.NY.postalCode': 98765,
}
}, { new: true });
// Output
{
name: 'Peter',
address: {
'NY.postalCode': 12345,
'CA.postalCode': 23456,
NY: {
postalCode: 98765,
}
}
}
I've tried using the escape character for dot (\u002e), but get the same output. Also, I have seen some new features for setting fields but only in Mongo v5: https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#mongodb-expression-exp.-setField. However, we're using Mongo v4.2 and would not be able to upgrade until a while later.
Would like to check if there are any means to $set nested attributes in address that don't completely replace the address object? i.e. to be able to specifically upsert an inner attribute within address object?
For example, is it possible to use the aggregation framework to workaround this?
I've tried something like the below, but it didn't work - not sure if its something that I did wrong or its probably not possible to use the aggregation pipeline..
Person.aggregate([
{
$match: {
_id: id,
},
},
{
$replaceRoot: { newRoot: {
$mergeObjects: [
'$$ROOT.address',
{
'NY.postalCode': 98765,
},
],
} },
},
]);
For your scenario, you need to achieve the update with aggregation pipeline.
Use $literal to escape the field name with dot.
Via $mergeObjects to merge current address object with { NY.postalCode': 98765 }.
db.collection.update({
"_id": ObjectId("5a934e000102030405000000")
},
[
{
$set: {
"address": {
$mergeObjects: [
"$address",
{
$literal: {
"NY.postalCode": 98765
}
}
]
}
}
}
],
{
new: true
})
Demo # Mongo Playground

how to update the name of multiple objects to lowercase in mongodb?

i need to update the all user who have the rol user and change the nickName to lowercase, i try with this code
const users = await userModel.updateMany({ rol:'user' },
{
$set:{
nickName: {
$toLower : "$nickName"
}
}
});
but is not working
In the future, please describe in what way your approach is not working. I went ahead and copied your example into this playground example and saw that your current approach resulted in the document being modified to include this field:
"nickName": {
"$toLower": "$nickName"
}
The problem here is that you are attempting to use the (aggregation) $set stage in your update (so that you can reference the existing $nickName field), but the update is using the $set (non-aggregation) modifier. If you wrap your second argument in square brackets (telling the database that you want to use an aggregation) then it should do what you want:
db.collection.update({
rol: "user"
},
[
{
$set: {
nickName: {
$toLower: "$nickName"
}
}
}
])
Playground demonstration here

How to build a MongoDB query that combines two field temporarily?

I have a schema which has one field named ownerId and a field which is an array named participantIds. In the frontend users can select participants. I'm using these ids to filter documents by querying the participantIds with the $all operator and the list of participantsIds from the frontend. This is perfect except that the participantsIds in the document don't include the ownerId. I thought about using aggregate to add a new field which consists of a list like this one: [participantIds, ownerId] and then querying against this new field with $all and after that delete the field again since it isn't need in the frontend.
How would such a query look like or is there any better way to achieve this behavior? I'm really lost right now since I'm trying to implement this with mongo_dart for the last 3 hours.
This is how the schema looks like:
{
_id: ObjectId(),
title: 'Title of the Event',
startDate: '2020-09-09T00:00:00.000',
endDate: '2020-09-09T00:00:00.000',
startHour: 1,
durationHours: 1,
ownerId: '5f57ff55202b0e00065fbd10',
participantsIds: ['5f57ff55202b0e00065fbd14', '5f57ff55202b0e00065fbd15', '5f57ff55202b0e00065fbd13'],
classesIds: [],
categoriesIds: [],
roomsIds: [],
creationTime: '2020-09-10T16:42:14.966',
description: 'Some Desc'
}
Tl;dr I want to query documents with the $all operator on the participantsIds field but the ownerId should be included in this query.
What I want is instead of querying against:
participantsIds: ['5f57ff55202b0e00065fbd14', '5f57ff55202b0e00065fbd15', '5f57ff55202b0e00065fbd13']
I want to query against:
participantsIds: ['5f57ff55202b0e00065fbd14', '5f57ff55202b0e00065fbd15', '5f57ff55202b0e00065fbd13', '5f57ff55202b0e00065fbd10']
Having fun here, by the way, it's better to use Joe answer if you are doing the query frequently, or even better a "All" field on insertion.
Additional Notes: Use projection at the start/end, to get what you need
https://mongoplayground.net/p/UP_-IUGenGp
db.collection.aggregate([
{
"$addFields": {
"all": {
$setUnion: [
"$participantsIds",
[
"$ownerId"
]
]
}
}
},
{
$match: {
all: {
$all: [
"5f57ff55202b0e00065fbd14",
"5f57ff55202b0e00065fbd15",
"5f57ff55202b0e00065fbd13",
"5f57ff55202b0e00065fbd10"
]
}
}
}
])
Didn't fully understand what you want to do but maybe this helps:
db.collection.find({
ownerId: "5f57ff55202b0e00065fbd10",
participantsIds: {
$all: ['5f57ff55202b0e00065fbd14',
'5f57ff55202b0e00065fbd15',
'5f57ff55202b0e00065fbd13']
})
You could use the pipeline form of update to either add the owner to the participant list or add a new consolidated field:
db.collection.update({},[{$set:{
allParticipantsIds: {$setUnion: [
"$participantsIds",
["$ownerId"]
]}
}}])

MongoDB aggregate only working with exact string

I am trying to implement a search feature to MongoDB and this is the aggregate pipeline I am using:
[
{
'$search': {
'text': {
'query': 'albus',
'path': [
'first_name', 'email', 'last_name'
]
}
}
}, {
'$project': {
'_id': 1,
'first_name': 1,
'last_name': 1
}
}, {
'$limit': 5
}
]
The command returns documents that contain only exactly albus or Albus, but return nothing for queries like alb, albu, etc. In the demo video I watched here: https://www.youtube.com/watch?time_continue=8&v=kZ77X67GUfk, the instructor was able to search based on substring.
The search index I am currently using is the default dynamic one.
How would I need to change my command?
You need to use the autocomplete feature, so your query will look like this:
{
$search: {
"autocomplete": {
'query': 'albus',
'path': [
'first_name', 'email', 'last_name'
]
}
}
}
Mind you both first_name, email and last_name need to be mapped as autocomplete type so a name like albus will be indexed as a, al, alb, albu, albus. Obviously this will vastly increase your index size.
Another thing to consider is tweaking the maxGrams and tokenization parameters. this will allow very long names to still work as expected and if you want to allow substring match like lbu matching albus.

Returning whole object in MongoDB aggregation

I have Item schema in which I have item details with respective restaurant. I have to find all items of particular restaurant and group by them with 'type' and 'category' (type and category are fields in Item schema), I am able to group items as I want but I wont be able to get complete item object.
My query:
db.items.aggregate([{
'$match': {
'restaurant': ObjectId("551111450712235c81620a57")
}
}, {
'$group': {
id: {
'$push': '$_id'
}
, _id: {
type: '$type'
, category: '$category'
}
}
}, {
$project: {
id: '$id'
}
}])
I have seen one method by adding each field value to group then project it. As I have many fields in my Item schema I don't feel this will good solution for me, Can I get complete object instead of Ids only.
Well you can always use $$ROOT providing that your server is MongoDB 2.6 or greater:
db.items.aggregate([
{ '$match': {'restaurant': ObjectId("551111450712235c81620a57")}},
{ '$group':{
_id : {
type : '$type',
category : '$category'
},
id: { '$push': '$$ROOT' },
}}
])
Which is going to place every whole object into the members of the array.
You need to be careful when doing this as with larger results you are certain to break BSON limits.
I would suggest that you are trying to contruct some kind of "search results", with "facet counts" or similar. For that you are better off running a separate query for the "aggregation" part and one for the actual document results.
That is a much safer and flexible approach than trying to group everything together.