Check if two property of a nested document are equal in mongodb - javascript

I have a user.expenses collection like this
{
userId: ObjectId("62f332b93753ac926ff6ac62"),
expenses: [
{
name: 'electricity',
assigned: 400,
given: 400,
},
{
name: 'restaurant',
assigned: 2100,
given: 0,
}
]
}
I will get userId and expenses.name(electricity) from the request. I need to check if the user.expenses collection has any expenses document whose name is electricity and assigned is not equal to given.
I used $elemMatch and could check if there are any embedded documents whose name is electricity.
db.user.expenses.find({
userId: ObjectId('62f332b93753ac926ff6ac62'),
expenses: {
$elemMatch: {
name: 'electricity',
},
},
});
EDIT
I also tried to use $where. But it only can be applied to the top-level document.

Query
you cant do it with query operators because you want to reference a field but you can do it with aggregate operators and $filter
filter the array and keep only if electricity and assigned!=given
keep the document if at least one sub-document was electricity with assigned!=given
Playmongo
aggregate(
[{"$match":
{"$expr":
{"$ne":
[{"$filter":
{"input": "$expenses",
"cond":
{"$and":
[{"$eq": ["$$this.name", "electricity"]},
{"$ne": ["$$this.assigned", "$$this.given"]}]}}}, []]}}}])

Related

Why is $elemMatch return the first document, instead of the all matches documents?

I'm trying to execute a query that returns all the documents that match based on query parameters.
I have the following schema:
_id: ObjectId('631b875491b16c38eecfa4e9')
brandName: "Nick"
categories: Array
products: Array
0: Object
productName: "Vans Shoes dsds Old Skool"
description: "amazing Shoes."
categoryId: ObjectId('62f3eaff3ded19dcce71081e')
price: 240
numberOfBuyers: 0
_id: ObjectId(631b875491b16c38eecfa4ec)
1: Object
2: Object
3: Object
__v: 0
The following query should give me all the documents that match, but it returns only the first document:
const products = await Brand.find(
{
_id: brandId
},
{
products: {
$elemMatch: {
categoryId: categoryId,
price: {
$gte: minPrice,
$lte: maxPrice
}
}
}
})
What is wrong?
You are querying on "Brand" documents. This means your query tells Mongoose: if one of the products is matching (categoryId and price), return the (whole) Brand document.
In order to retrieve only specific elements of this array, you should include your $elemMatch object in the projection step of your find call:
const products = await Brand.find({
_id: brandId
}, {
//include other properties you want to include in your output document
products: {
$elemMatch: {
categoryId: "62f3eaff3ded19dcce71081e",
price: 240
}
}
}
})
Update after comment
Your products array will only contain the first element that was matched. This is intended behaviour (as described here: cs/manual/reference/operator/projection/elemMatch/):
Definition
$elemMatch
The
$elemMatch
operator limits the contents of an field from the query results to contain only the first element matching the
$elemMatch
condition.
In order to get several results you should probably use an aggregation pipeline using $unwind and $group.

Mongo error :Modifiers operate on fields but we found type array instead

I'm working with node and mongo 5.0 . I have a preexisting record that I want to add 2 fields to. I am trying to insert 2 numbers (due, assessed) into the fields Owed and Yearly. These 2 fields do not currently exist within each record. I tried multiple variations including:
await collection.updateOne({ _id: record._id }, { $set: [{ "Owed": due}, {"Yearly": assessed }]});
which gives:
'MongoServerError: Modifiers operate on fields but we found type array instead. For example: {$mod: {<field>: ...}} not {$set: [ { Owed: 0 }, { Yearly: 466.64 } ]}\n
How to I add multiple fields to a record here?
Edit:
I changed my code to yours. Now I am seeing:
MongoServerError: Modifiers operate on fields but we found type array instead. For example: {$mod: {<field>: ...}} not {$set: [ { Owed: 0 }, { Yearly: 466.64 } ]}
Any thoughts?
I guess you should pass 3rd paramater upsert to create field if it doesn't exists.
await collection.updateOne({ _id: record._id }, { $set: [{ "Owed": due}, {"Yearly": assessed }]}, {upsert: true});
Based on https://www.mongodb.com/docs/manual/reference/operator/update/set/#set-top-level-fields I tried:
await collection.updateOne({ _id: record._id }, { $set: { "Owed": due, "Yearly": assessed }});
This appears to work

How to query an object inside an array by multiple value in mongoose (javascript)?

I have an object and the server will receive a user_id and a part_id. I need to get the certain user, then filter the parts by the provided part ID and get the price.
{
_id: 6086b8eec1f5325278846983,
user_id: '13',
car_name: 'Car name 1',
created_at: 2008-11-25T00:46:52.000Z,
parts: [
{
_id: 6086ee212681320190c3c8e0,
part_id: 'P456',
part_name: 'Part name 1',
image: 'image url',
stats: {
price: 10,
}
},
{
_id: 6087e7795e2ca925fc6ead27,
part_id: 'P905',
part_name: 'Part name 2',
image: 'image url',
stats: {
price: 15,
}
}
]
}
I tried to run the following, but ignores the part_id filter and returns every parts in the array.
Custumers.findOne({'user_id': '13', 'parts.part_id': 'P456'})
Also tried with aggregate but still no luck.
Customers.aggregate([
{ $match: { 'user_id': '13'}}
]).unwind('parts')
I checked the mongoose documentation but cannot wrap my head around it. Please let me know what I am missing.
Mongoose version: 5.12.4
Option - 1
This will work if you've only 1 matching parts
$ (projection)
The $ operator projects the first matching array element from each document in a collection based on some condition from the query statement.
Demo - https://mongoplayground.net/p/tgFL01fK3Te
db.collection.find(
{ "user_id": "13", "parts.part_id": "P456" },
{ "parts.$": 1, car_name: 1 } // add fields you need to projection
)
Option -2
$unwind
Demo - https://mongoplayground.net/p/_PeP0WHVpJH
db.collection.aggregate([
{
$match: {
"user_id": "13",
"parts.part_id": "P456"
}
},
{
$unwind: "$parts" // break into individual documents
},
{
$match: {
"parts.part_id": "P456"
}
}
])

Insert array of objects into MongoDB

I wonder how I could insert array of objects to Mongo collection "root-level documents" with own pre-defined _id values.
I have tried db.MyCollection.insert(array); but it creates nested documents under one single generated _id in MongoDB.
var array = [
{ _id: 'rg8nsoqsxhpNYho2N',
goals: 0,
assists: 1,
total: 1 },
{ _id: 'yKMx6sHQboL5m8Lqx',
goals: 0,
assists: 1,
total: 1 }];
db.MyCollection.insert(array);
What I want
db.collection.insertMany() is what you need (supported from 3.2):
db.users.insertMany(
[
{ name: "bob", age: 42, status: "A", },
{ name: "ahn", age: 22, status: "A", },
{ name: "xi", age: 34, status: "D", }
]
)
output:
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("57d6c1d02e9af409e0553dff"),
ObjectId("57d6c1d02323d119e0b3c0e8"),
ObjectId("57d6c1d22323d119e0b3c16c")
]
}
Why not iterate over the array objects, and insert them one at a time?
array.forEach((item) => db.MyCollection.insert(item));
Go through this Link To get Exact Outcome the way you want:
https://docs.mongodb.org/manual/tutorial/insert-documents/#insert-a-document
You can use MongoDB Bulk to insert multiple document in one single call to the database.
First iterate over your array and call the bulk method for each item:
bulk.insert(item)
After the loop, call execute:
bulk.execute()
Take a look at the refereed documentation to learn more.

MongoDB: projection to match multiple

I am having a bit of trouble here. So I want to show a user's profile. The user belongs to groups. The logged in user can see details of any groups they have in common. Here is some example data
{
_id: "1234",
battletag: "Fake#1234",
guilds: [{
name: "Lok'Narosh!",
rank: 4,
roles: ['casual']
}, {
name: "Warlords of Draenor",
rank: 2,
roles: ['PvP', 'raider']
}, {
name: "Lok'Tar Ogar!",
rank: 3,
roles: ['raider']
}],
}
I can get the current user's groups and reduce it to ['Lok'Narosh!', 'Warlords of Draenor'], meaning that Lok'tar Ogar should be omitted from the results.
The main problem I am coming across is that most operations I know only return the first result. For example, with $elemMatch:
The $elemMatch operator limits the contents of an field from the query results to contain only the first element matching the $elemMatch condition.
Is there a way that I can filter this list to contain all matching elements against a list of elements?
You can use aggregate:
$unwind operator to deconstruct 'guilds' field.
Apply criteria with $match
Reconstruct array.
db.getCollection('yourColl').aggregate({$unwind:"$guilds"},{$match:{"guilds.rank":{$gte:2.0}}},{$group:{ "_id":"$_id", "battletag":{$first:"$battletag"},"guilds":{$addToSet:"$guilds"}}})

Categories

Resources