MongoDB - Autocomplete - Get all words starting with X - mongodb

I have a collection (users) with the following structure:
{
propA: {
words: ["i", "have", "an","important", "question"]
}
}
I want to get autocomplete options from the db for some input in my website.
So first, i think that i need to create an index for propA.words.
Maybe something like this(?):
db.users.createIndex({ "propA.words" : 1 })
Second, how can i query this index to get all the words starting with X?
For example, for the string "i", the query will retrieve ["i", "important"].
Thanks!
EDIT:
This is the collection:
{
propA: {
words: ["aa","bb","cc","dd"]
}
}
{
propA: {
words: ["ab"]
}
}
{
propA: {
words: []
}
}
{
propB: []
}
Now, i want a query to get all the words that starts with "a".
The query should return ["aa","ab"] on the above collection.
I want the query to use only the index so the search will be efficient.

You can use this aggregation, which iterates over the words array and matches the regex search string.
db.collection.aggregate( [
{
$addFields: {
matches: {
$filter: {
input: "$propA.words",
as: "w",
cond: {
$regexMatch: { input: "$$w" , regex: "^i" }
}
}
}
}
}
] )
The output:
{
"_id" : 1,
"propA" : {
"words" : [
"i",
"have",
"an",
"important",
"question"
]
},
"matches" : [
"i",
"important"
]
}
[ EDIT ADD ]
Now, i want a query to get all the words that starts with "a". The
query should return ["aa","ab"] on the above collection. I want the
query to use only the index so the search will be efficient.
The aggregation:
db.collection.aggregate( [
{
$match: { "propA.words": { $regex: "^a" } }
},
{
$unwind: "$propA.words"
},
{
$group: {
_id: null,
matchedWords: {
$addToSet: {
$cond: [ { $regexMatch: { input: "$propA.words", regex: "^a" } },
"$propA.words",
"$DUMMY" ]
}
}
}
},
{
$project: { _id: 0 }
}
] )
The result:
{ "matchedWords" : [ "ab", "aa" ] }
Index usage:
The index is created on the collection as follows:
db.collection.createIndex( { "propA.words": 1 } )
You can verify the index usage on the aggregation's $match stage by applying the explain and generating a query plan. For example:
db.collection.explain("executionStats").aggregate( [ ... ] )

yes you make an index on the field, which is an array. then use regex query - the symbol ^ for 'starts with'... an index on an array field can create a big load... but your query being a 'start-with' is an efficient design....

Related

Moving element from one array to another, using the same Mongo UpdateOne query

I have the following data model:
"finances" : {
"futurePostings" : [
{
"description" : "test",
"orderId" : ObjectId("614702b9e98e83bc5d7d3d62")
}
],
"balance" : []
}
Then, I'm trying to move the element inside futurePosting to balance. I could remove it from futurePostings, but I can't figure out if would be possible to use the positional $ operator (or any other command) to push this same document inside balance, via the same query.
db.collection.updateOne(
{
"finances.futurePostings.orderId": ObjectId(orderId),
},
{
$push: { "finances.balance": ?? },
$pull: {
"finances.futurePostings": { orderId: ObjectId(orderId) },
},
}
);
Is it possible?
It is not possible in a regular update query, but you can try update with aggregation pipeline starting from MongoDB 4.2,
pull from futurePostings
$filter to iterate loop of futurePostings array and check not equal to condition to remove provided orderId
push into balance
$filter to iterate loop of futurePostings array and check equal to condition and filter matching orderId element
$concatArrays to concat current balance array and new element from above filtered result
db.collection.updateOne(
{ "finances.futurePostings.orderId": ObjectId(orderId) },
[{
$set: {
"finances.futurePostings": {
$filter: {
input: "$finances.futurePostings",
cond: {
$ne: ["$$this.orderId", ObjectId(orderId)]
}
}
},
"finances.balance": {
$concatArrays: [
"$finances.balance",
{
$filter: {
input: "$finances.futurePostings",
cond: {
$eq: ["$$this.orderId", ObjectId(orderId)]
}
}
}
]
}
}
}]
)
Playground

MongoDb 4.x Query with projection without empty entries using find

We are now using mongo-db to store data from tests. I am using Mongo-Shell
The document is structured like this:
{
static1:"abc",
static2:"xyz",
static3:"asd", [...],
nested:[
{
data1: "d1",
data2: "d2",
},
{
data1: "dx",
data4: "d4",
data5: "d5",
data6: "d6",
},
{
data1: "ds",
data8:"data8"
}, [...]
]
}
So the static-data is always in the same structure, but for every measurement the object can look different. It could be voltage with upper, lower and actual value. Or just comparison of a target value and an actual value. Highly dynamic.
Same data1-name represent same attributes.
Now e.g. I want to display only some static data and one (or several) attributes of a nested document.
I am using this query:
find({}, {_id:0, data1:1, "nested.data8":1}).pretty()
As expected, only the static-data1 is displayed, but the dynamic measurements has lots of empty objects in the shell-output
Example-Output:
{
"static1" : "123",
"nested" : [
{ },
{ },
{ }, [...] ,
{ "data8" : "OK" }
] }
The desired output would be:
{
"static1" : "123",
"nested" : [
{ "data8" : "d8" }
]
}
I also tried this query on the mongo shell:
aggregate( { $addFields: {"static":"$static1", "data8":"$nested.data8"} }, { $project:{"static1":1, "nested.data8":1} } ).pretty()
But the result is the same.
I hope there is a ways to get rid of the empty documents in the output.
Thanks
You can try $filter operator to filter result of nested array by checking condition not equal to empty object {}
db.collection.aggregate([
{
$project: {
_id: 0,
data1: 1,
"nested.data8": 1
}
},
{
$set: {
nested: {
$filter: {
input: "$nested",
cond: { $ne: ["$$this", {}] }
}
}
}
}
])
Playground

find and update with pull and get the pulled document in return

Here is my collection
[
{_id:1,
persons:[{name:"Jack",age:12},{name:"Ma",age:13}]
}
]
I want to remove {name:"Jack",age:12} in persons by pull but I also want after pulling is completed I will be returned the pulled {name:"Jack",age:12}. How can I do this?
I want to do it like this
db.getCollection('test').findOneAndUpdate({_id:1},{$pull:{"persons":{name:"Jack"}}},
{projection:{"person":{
$filter : {
input: "$persons",
as : "ele",
cond : {$eq : ["$$ele.name","Jack"]}
}
}}})
You can use $reduce, because $filter will return array, also aggregation array operators will support from MongoDB 4.4,
db.getCollection('test').findOneAndUpdate({_id:1},
{ $pull: { "persons": { name: "Jack" } } },
{
projection: {
"person":{
$reduce: {
input: "$persons",
initialValue: {},
in: { $cond: [{$eq: ["$$this.name","Jack"]}, "$$this", "$$value"] }
}
}
}
}
)
Playground

Mongodb $expr on array

Mongodb: 4.0.13
I'm having troubles in understand and get working $expr with arrays.
Let' start and create a new collection (dbRepeatElement) with following document:
db.testRepeatElement.insert([
{
"data" : {
"FlsResSemires_2" : {
"Sospensione" : [
{
"DataInizio" : 1548806400000,
"DataFine" : 1549065600000,
"Motivazione" : "1"
}
]
}
},
"derived" : {
"DATAFINEANNORIFERIMENTO" : 1609372800000,
"regione190" : "190",
"REGAOEROG" : "190209820300",
"REGASLEROG" : "190209"
}
}
])
In a bigger aggregation, following part is not working:
db.testRepeatElement.aggregate([
{
$match: {
$expr: {
$gt: ["$data.FlsResSemires_2.Sospensione.DataInizio", "$derived.DATAFINEANNORIFERIMENTO"]
}
}
}
])
Result: return a match ( wrong! just check dates)
Reading mongodb documentation seems to be, using combination with arrays, aggregation and $expr does not return expected result and you have to specify with element of the array you want to check, like:
db.testRepeatElement.aggregate([
{
$match: {
$expr: {
$gt: ["$data.FlsResSemires_2.0.Sospensione.DataInizio", "$derived.DATAFINEANNORIFERIMENTO"]
}
}
}
])
Result: return no match (right!)
Question: my requirement is to check every element in the array, so how to solve this, without using $unwind? Why there is this kind of result ?
The $filter aggregation operator is used to do the match operation on array elements. The following aggregation query will result only the Sospensione array elements which match the $gt condition:
db.testRepeatElement.aggregate( [
{
$addFields: {
"data.FlsResSemires_2.Sospensione": {
$filter: {
input: "$data.FlsResSemires_2.Sospensione",
cond: {
$gt: [ "$$this.DataInizio", "$derived.DATAFINEANNORIFERIMENTO" ]
}
}
}
}
},
{
$match: {
$expr: {
$gt: [ { $size: "$data.FlsResSemires_2.Sospensione" }, 0 ]
}
}
}
] ).pretty()

MongoDB : Retrieve Associated Value from Object in an Array of Arrays

In mongo I have a documents that follow the below pattern :
{
name: "test",
codes: [
[
{
code: "abc",
value: 123
},
{
code: "def",
value: 456
},
],
[
{
code: "ghi",
value: 789
},
{
code: "jkl",
value: 012
},
]
]
}
I'm using an aggregate query (because of joins) and in a $project block I need to return the "name" and the value of the object that has a code of "def" if it exists and an empty string if it doesn't.
I can't simply $unwind codes and $match because the "def" code is not guaranteed to be there.
$filter seems like the right approach as $elemMatch doesn't work, but its not obvious to me how to do this on nested array of arrays.
You can try below query, instead of unwinds & filter this can give you required result with less docs to operate on :
db.collection.aggregate([
/** merge all arrays inside codes array into code array */
{
$addFields: {
codes: {
$reduce: {
input: '$codes',
initialValue: [],
in: { $concatArrays: ["$$value", "$$this"] }
}
}
}
},
/** project only needed fields & value will be either def value or '',
* if 'def' exists in any doc then we're check index of it to get value of that particular object using arrayElemAt */
{
$project: {
_id:0, name: 1, value:
{
$cond: [{ $in: ["def", '$codes.code'] }, { $arrayElemAt: ['$codes.value', { $indexOfArray: ["$codes.code", 'def'] }] }, '']
}
}
}])
Test : MongoDB-Playground