I have the following MongoDB model:
const Relation = mongoose.model('Relation',{
name :{
type: String,
},
port:{
type: Number,
},
services: { type : Array , "default" : [] }
});
Each port is a unique number for each document.
A collection could have the following values:
{
"port":"116", //unique number
"name":"xzy",
services: [
{"id":'1', "trust":"good"},
{"id":'2', "trust":"bad"},
]
}
How can for example make the "trust" value "bad" for the object with the "id"= 1 ??
I assume I should first find the collection that matchs the port number "116" and then find the object inside the Services array that has the "id" of 1.
How can I do that in mongoose?
You can use $ positional operator to update value inside an array
Relation.findOneAndUpdate(
{ "port": "116", "services.id": "1" },
{ "$set": { "services.$.trust": "bad" }}
)
Related
This question already has answers here:
How to return only value of a field in mongodb
(6 answers)
Closed 3 months ago.
I am using mongodb with mongoose in NodeJs project.
I want to convert my data array of object to array of string like
[
{
id: '6375ce97e8ec382b8dbf8433',
},
{
id: '638de425cbb676123ad204f6',
},
{
id: '638de425cbb676123ad20509',
},
{
id: '6375ce97e8ec382b8dbf8433',
},
{
id: '638dc4cc48b1de03d0d920b7',
},
{
id: '638de425cbb676123ad204f6',
},
{
id: '6375ce97e8ec382b8dbf8433',
},
];
Convert to
['6375ce97e8ec382b8dbf8433',
'638de425cbb676123ad204f6',
'638de425cbb676123ad20509',
'6375ce97e8ec382b8dbf8433',
'638dc4cc48b1de03d0d920b7',
'638de425cbb676123ad204f6',
'6375ce97e8ec382b8dbf8433']
How I convert my data using mongodb not JavaScript method like, map, filter, find etc
I did not convert the data because lack of mongodb knowledge.
You cannot do this directly within mongodb. The reason is that a document must be an object. Eg, you cannot replace the object with a string. Instead, you have 3 options:
1 - Get the output very close, but you just have to locate the array: https://mongoplayground.net/p/furNa_ezJ5l
db.collection.aggregate([
{
$group: {
_id: null,
data: {
$push: "$$ROOT.id"
}
}
}
])
Gives you:
[
{
"_id": null,
"data": [
"6375ce97e8ec382b8dbf8433",
"638de425cbb676123ad204f6",
"638de425cbb676123ad20509",
"6375ce97e8ec382b8dbf8433",
"638dc4cc48b1de03d0d920b7",
"638de425cbb676123ad204f6",
"6375ce97e8ec382b8dbf8433"
]
}
]
Where the data you want will be:
const stringArray = result[0].data
2 - Use JS after your query
db.collection.find({}).toArray().map( function(u) { return u.id } )
3 - Magic distinct values
Assuming that your values of id are always unique and that you don't want to do any further querying or aggregation, you can ask mongo for the unique ones:
db.collection.distinct('id')
Which should give you:
['6375ce97e8ec382b8dbf8433',
'638de425cbb676123ad204f6',
'638de425cbb676123ad20509',
'6375ce97e8ec382b8dbf8433',
'638dc4cc48b1de03d0d920b7',
'638de425cbb676123ad204f6',
'6375ce97e8ec382b8dbf8433']
My data model:
{
_id: ObjectId,
persons:[{
_id: ObjectId,
name: String,
...
}],
relations: [{
type: String,
personId: ObjectId,
...
}],
...
}
Here's my issue:
I am trying to find documents where person's name is x and it's _id is inside the relations array (personId) with a given type.
Example:
My data:
[{
_id:"1",
persons:[{
_id:"1",
name: "Homer"
},
{
_id:"2",
name: "Bart"
}],
relations: [{
type:"House_Owner",
personId: 1,
}],
}]
Request_1:
Find all documents where "Homer" is the house owner
Result:
[{
_id:"1",
...
}]
Request_2:
Find all documents where "Bart" is the house owner
Result:
[]
Any help would be appreciated.
The only solution I see here is to do the find operation with the given name value and after that filter the mongodb result.
PS: I cannot change the existing data model
EDIT:
I found a solution to do this by using $where operator with a javascript function but I am not sure that's the most efficient way.
db.myCollection("x").find({
$where: function() {
for (const relation of this.relations) {
if(relation.type === "House_Owner") {
for (const person of this.persons) {
if(person.name === "Homer" && person._id.equals(relation.personId)) {
return true;
}
}
}
}
}
})
You can do something like this:
const requiredName="x"
const requiredId = "id"
await yourModel.find({$and:[{"relations.personId":requiredId },{"persons.name":requiredName}]})
I have a collection named group on the mongoDb database . The collection includes 2 or more object and each object contains an array named members . what is the best possible and efficient way to concate and get all the members data from the database. what would be the mongoDB query ?
my collection looks like
[
{
id: ObjcetId("15215252"),
groupName: "travellers of Bangladesh",
members: ["1","2","3"]
},
{
id: ObjcetId("32643724362"),
groupName: "People from Bangladesh",
members: ["4","5","6"]
}
]
and i Want just this exact data
members: ["1","2","3","4","5","6"]
Use can use aggregations
$unwind to deconstruct the array
$group to reconstruct the array
Here is the code
db.collection.aggregate([
{ $unwind: "$members" },
{
$group: {
_id: null,
members: { $push: "$members" }
}
}
])
Working Mongo playground
I have a faunadb collection of users. The data is as follows:
{
"username": "Hermione Granger",
"fullName": "Hermione Jean Granger",
"DOB": "19-September-1979",
"bloodStatus": "Muggle-Born",
"gender": "Female",
"parents": [
"Wendell Wilkins",
"Monica Wilkins"
]
}
when I use an index I have to search for the whole phrase i.e. Hermione Granger. But I want to search for just Hermione and get the result.
I came across a solution that seems to work.
The below uses the faunadb client.
"all-items" is an index setup on a collection in Fauna that returns all items in the collection
The lambda is searching on the title field
This will return any document with a title that partially matches the search term.
I know this is a bit late; I hope it helps anyone else who may be looking to do this.
const response = await faunaClient.query(
q.Map(
q.Filter(
q.Paginate(q.Match(q.Index("all_items"))),
q.Lambda((ref) =>
q.ContainsStr(
q.LowerCase(
q.Select(["data", "title"], q.Get(ref))
),
title // <= this is your search term
)
)
),
q.Lambda((ref) => q.Get(ref))
)
The Match function only applies an exact comparison. Partial matches are not supported.
One approach that might work for you is to store fields that would contain multiple values that need to be indexed as arrays.
When you index a field whose value is an array, the index creates multiple index entries for the document so that any one of the array items can be used to match entries. Note that this strategy increases the read and write operations involved.
Here's an example:
> CreateCollection({ name: "u" })
{
ref: Collection("u"),
ts: 1618532727920000,
history_days: 30,
name: 'u'
}
> Create(Collection("u"), { data: { n: ["Hermione", "Granger"] }})
{
ref: Ref(Collection("u"), "295985674342892032"),
ts: 1618532785650000,
data: { n: [ 'Hermione', 'Granger' ] }
}
> Create(Collection("u"), { data: { n: ["Harry", "Potter"] }})
{
ref: Ref(Collection("u"), "295985684233060864"),
ts: 1618532795080000,
data: { n: [ 'Harry', 'Potter' ] }
}
> Create(Collection("u"), { data: { n: ["Ginny", "Potter"] }})
{
ref: Ref(Collection("u"), "295985689713967616"),
ts: 1618532800300000,
data: { n: [ 'Ginny', 'Potter' ] }
}
> CreateIndex({
name: "u_by_n",
source: Collection("u"),
terms: [
{ field: ["data", "n"] }
]
})
{
ref: Index("u_by_n"),
ts: 1618533007000000,
active: true,
serialized: true,
name: 'u_by_n3',
source: Collection("u"),
terms: [ { field: [ 'data', 'n' ] } ],
partitions: 1
}
> Paginate(Match(Index("u_by_n"), ["Potter"]))
{
data: [
Ref(Collection("u"), "295985684233060864"),
Ref(Collection("u"), "295985689713967616")
]
}
Note that you cannot query for multiple array items in a single field:
> Paginate(Match(Index("u_by_n"), ["Harry", "Potter"]))
{ data: [] }
The reason is that the index has only one field defined in terms, and successful matches require sending an array having the same structure as terms to Match.
To be able to search for the full username and the username as an array, I'd suggest storing both the string and array version of the username field in your documents, e.g. username: 'Hermione Granger' and username_items: ['Hermione', 'Granger']. Then create one index for searching the string field, and another for the array field, then you can search either way,
The following code works great in that it updates the object in the nested array.
However, I'm struggling to find a way to push a new object (Ex. {"locale" : "ar" , value:"مرحبا"}) if locale does not exist or update value if locale already exists (Ex. {"locale" : "en" , value:"hello"})
Update code:
Project.findOneAndUpdate(
{_id:projectId, 'sections._id': sectionId},
{ "$set": { "sections.$.subheader": {"locale":args.lang,"value":args.title} }},
{ upsert : true, new: true, useFindAndModify: false },
(err, section) => {
}
)
Object structure:
"project": {
"name": "project name",
"sections": [
{
"subheader": [{
'locale' : "en",
'value' : "Helle"
},
{
'locale' : "fr",
'value' : "salut"
}]
}
]
}
Unfortunately, this is not possible to do in one go. The upsert option only works on objects in the collection, not on nested objects.
You could solve this by first trying to update the element in the array, then check if the object in the nested array was matched. If there was no match, you can insert it into the nested array using $addToSet.
Additionally, you need to use positional operators to match the nested arrays:
Project.findOneAndUpdate(
// match item in subheader array
{ _id: projectId, 'sections._id': sectionId, 'sections.subheader.locale': args.lang },
// update existing item in subheader array
{ "$set": { "sections.$[section].subheader.$[subheader].value": args.title } },
// we use arrayFilters here, don't use upsert now
{ arrayFilters: [{ 'section._id': sectionId }, { 'subheader.locale': args.lang }], useFindAndModify: false },
(err, section) => {
// check if section was found
if (!section) {
// add new object to array if it wasn't found yet
Project.findOneAndUpdate(
// match section
{ _id: projectId, 'sections._id': sectionId},
// add new object to array
{ "$addToSet": { "sections.$.subheader": {"locale": args.lang,"value": args.title } }},
(err, section) => {
console.log('created new locale')
}
)
} else {
console.log('updated existing locale')
}
}
)