I am trying to update existing array element by adding new fields into it.
...
{
"_id": "f08b466a-163b-4d9e-98f5-d900ef0f1a26",
"firstName": "foo",
"result": [
{
"_id":"957ee97d-d461-4d6c-8a80-57351bdc29f7",
"subjectName":"Mathematics",
"marks": 60
},
{
"_id":"0591d9a0-fd0f-4876-9bd3-dec4d5ab452e",
"subjectName":"Science",
"marks": 70
},
{
"_id":"21f42104-791b-4522-81ce-f7ae1b30d075",
"subjectName":"Social science",
"marks": 50
}
]
},
...
Now I want to add new field to science subject called "isFavorite: true"
like,
{
"_id": "f08b466a-163b-4d9e-98f5-d900ef0f1a26",
"firstName": "foo",
"result": [
{
"_id":"957ee97d-d461-4d6c-8a80-57351bdc29f7",
"subjectName":"Mathematics",
"marks": 60
},
{
"_id":"0591d9a0-fd0f-4876-9bd3-dec4d5ab452e",
"subjectName":"Science",
"marks": 70
"isFavorite": true #-----------------New field----------
},
{
"_id":"21f42104-791b-4522-81ce-f7ae1b30d075",
"subjectName":"Social science",
"marks": 50
}
]
},
...
What I tried so far?
from pymongo import MongoClient
...
collection = mongoInstance["student"]
student = collection.find_one({"_id": "f08b466a-163b-4d9e-98f5-d900ef0f1a26"})
for result in student["result"]:
if result["_id"] == "0591d9a0-fd0f-4876-9bd3-dec4d5ab452e":
result["isFavorite"] = True
break
collection.update_one({"_id": "f08b466a-163b-4d9e-98f5-d900ef0f1a26"}, {"$set": student })
This is working, but I believe there might be simple way to just find student document by it's id and adding new field to array item by item._id.
Looking for some elegant Mongodb query to find and update specific array element.
#Alex Blex was on the right lines regarding the positional operator; the pymongo syntax is very similar:
db.mycollection.update_one({'_id': 'f08b466a-163b-4d9e-98f5-d900ef0f1a26',
'result._id': '0591d9a0-fd0f-4876-9bd3-dec4d5ab452e'},
{'$set': {'result.$.isFavorite': True}})
Full example using the sample data provided:
from pymongo import MongoClient
import pprint
db = MongoClient()['mydatabase']
db.mycollection.insert_one({
'_id': 'f08b466a-163b-4d9e-98f5-d900ef0f1a26',
'firstName': 'foo',
'result': [
{
'_id': '957ee97d-d461-4d6c-8a80-57351bdc29f7',
'subjectName': 'Mathematics',
'marks': 60
},
{
'_id': '0591d9a0-fd0f-4876-9bd3-dec4d5ab452e',
'subjectName': 'Science',
'marks': 70
},
{
'_id': '21f42104-791b-4522-81ce-f7ae1b30d075',
'subjectName': 'Social science',
'marks': 50
}
]
})
db.mycollection.update_one({'_id': 'f08b466a-163b-4d9e-98f5-d900ef0f1a26',
'result._id': '0591d9a0-fd0f-4876-9bd3-dec4d5ab452e'},
{'$set': {'result.$.isFavorite': True}})
pprint.pprint(list(db.mycollection.find())[0])
result:
{'_id': 'f08b466a-163b-4d9e-98f5-d900ef0f1a26',
'firstName': 'foo',
'result': [{'_id': '957ee97d-d461-4d6c-8a80-57351bdc29f7',
'marks': 60,
'subjectName': 'Mathematics'},
{'_id': '0591d9a0-fd0f-4876-9bd3-dec4d5ab452e',
'isFavorite': True,
'marks': 70,
'subjectName': 'Science'},
{'_id': '21f42104-791b-4522-81ce-f7ae1b30d075',
'marks': 50,
'subjectName': 'Social science'}]}
Related
Below is my code to display review array data which is part of the restaurant collection object:
async get(reviewId) {
const restaurantsCollection = await restaurants();
reviewId = ObjectId(reviewId)
const r = await restaurantsCollection.findOne(
{ reviews: { $elemMatch: { _id : reviewId } } },
{"projection" : { "reviews.$": true }}
)
return r
}
My object looks like:
{
_id: '6176e58679a981181d94dfaf',
name: 'The Blue Hotel',
location: 'Noon city, New York',
phoneNumber: '122-536-7890',
website: 'http://www.bluehotel.com',
priceRange: '$$$',
cuisines: [ 'Mexican', 'Italian' ],
overallRating: 0,
serviceOptions: { dineIn: true, takeOut: true, delivery: true },
reviews: []
}
My output looks like:
{
"_id": "6174cfb953edbe9dc5054f99", // restaurant Id
"reviews": [
{
"_id": "6176df77d4639898b0c155f0", // review Id
"title": "This place was great!",
"reviewer": "scaredycat",
"rating": 5,
"dateOfReview": "10/13/2021",
"review": "This place was great! the staff is top notch and the food was delicious! They really know how to treat their customers"
}
]
}
What I want as output:
{
"_id": "6176df77d4639898b0c155f0",
"title": "This place was great!",
"reviewer": "scaredycat",
"rating": 5,
"dateOfReview": "10/13/2021",
"review": "This place was great! the staff is top notch and the food was delicious! They really know how to treat their customers"
}
How can I get the output as only the review without getting the restaurant ID or the whole object?
So the query operators, find and findOne do not allow "advanced" restructure of data.
So you have 2 alternatives:
The more common approach will be to do this in code, usually people either use some thing mongoose post trigger or have some kind of "shared" function that handles all of these transformations, this is how you avoid code duplication.
Use the aggregation framework, like so:
const r = await restaurantsCollection.aggregate([
{
$match: { reviews: { $elemMatch: { _id : reviewId } } },
},
{
$replaceRoot: {
newRoot: {
$arrayElemAt: [
{
$filter: {
input: "$reviews",
as: "review",
cond: {$eq: ["$$review._id", reviewId]}
}
},
0
]
}
}
}
])
return r[0]
Issue
I have a pouchdb-express server I am using for some tests for a CouchDB database.
I have the following database with an item like so:
{
_id: "12345",
email: 'u0#email.com',
companies: [{ id: 'company', uid: 'u0' }]
}
I then run the following command that works on my API hitting the CouchDB database but does not work when I try it on the PouchDB Express server.
.find({selector: { "companies": { "$elemMatch": {id: "company", uid: "u0"} } }})
I get the following error:
{ error: 'bad_request',
reason: 'unknown operator "0" - should be one of $eq, $lte, $lt, $gt, $gte, $exists, $ne, $in, $nin, $size, $mod, $regex, $elemMatch, $type, $allMatch or $all',
name: 'bad_request',
status: 400,
message: 'unknown operator "0" - should be one of $eq, $lte, $lt, $gt, $gte, $exists, $ne, $in, $nin, $size, $mod, $regex, $elemMatch, $type, $allMatch or $all' }
I also get the same exact error during the following query:
.find({
limit:9999999,
selector:{
$or: [
{$and: [{type:"something"},{uid:"u0"}] },
{$and: [{type:"document"}]}
]
}
})
I've also tried doing $eq almost exactly like the test suite does with still no dice.
Does anyone know why this would happen?
Info
Environment: Node.JS
Server: PouchDB Server
Here is my package.json
"pouchdb-find": "^6.4.3", // tried 7.0.0 and still have the issue
"pouchdb-node": "^6.4.3",
"pouchdb-server": "^4.1.0",
I have tinkered with this and discovered that for records like:
{
"borough": "Brooklyn",
"cuisine": "American ",
"marks": [50, 60, 45, 43],
"grades": [
{
"grade": "A",
"score": 5
},
{
"grade": "A",
"score": 7
},
{
"grade": "A",
"score": 12
},
{
"grade": "A",
"score": 12
}
],
"name": "Riviera Caterer"
},
This simple selector will return the correct results :
{ selector: { '$elemMatch': { '$gte': 0, '$lt': 30 } } }
and, while this "composite (?)" selector, ignores mismatches, and returns all rows!! ...
{ selector: 'grades': { '$elemMatch': { 'score': 14 } } }
... this one bombs with the error you indicate :
{ selector: 'grades': { '$elemMatch': { "grade": "B" } } }
I suspect the "pouchdb-find" version of $elemMatch can only handle simple arrays, not arrays of objects.
Seems like a PR is required :-(
I am trying to query an embedded subdocument and then only return an array in that subdocument via projection. After a query you can select fields that you want returned via projection. I want to use the native functionality because it is possible and the most clean way. The problem is it returns arrays in two documents.
I tried different query and projection options, but no result.
User model
// Define station schema
const stationSchema = new mongoose.Schema({
mac: String,
stationName: String,
syncReadings: Boolean,
temperature: Array,
humidity: Array,
measures: [{
date: Date,
temperature: Number,
humidity: Number
}],
lastUpdated: Date
});
// Define user schema
var userSchema = mongoose.Schema({
apiKey: String,
stations : [stationSchema]
}, {
usePushEach: true
}
);
api call
app.get('/api/stations/:stationName/measures',function(req, res, next) {
var user = {
apiKey: req.user.apiKey
}
const query = {
apiKey: user.apiKey,
'stations.stationName': req.params.stationName
}
const options = {
'stations.$.measures': 1,
}
User.findOne(query, options)
.exec()
.then(stations => {
res.status(200).send(stations)
})
.catch(err => {
console.log(err);
res.status(400).send(err);
})
});
Expected result
{
"_id": "5c39c99356bbf002fb092ce9",
"stations": [
{
"stationName": "livingroom",
"measures": [
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:45.468Z",
"_id": "5c3a6f09fd357611f8d078a0"
},
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:46.500Z",
"_id": "5c3a6f0afd357611f8d078a1"
},
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:47.041Z",
"_id": "5c3a6f0bfd357611f8d078a2"
}
]
}
]
}
Actual result
{
"_id": "5c39c99356bbf002fb092ce9",
"stations": [
{
"stationName": "livingroom",
"measures": [
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:45.468Z",
"_id": "5c3a6f09fd357611f8d078a0"
},
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:46.500Z",
"_id": "5c3a6f0afd357611f8d078a1"
},
{
"humidity": 60,
"temperature": 20,
"date": "2019-01-12T22:49:47.041Z",
"_id": "5c3a6f0bfd357611f8d078a2"
}
]
},
******************************************************
// this whole object should not be returned
{
"stationName": "office",
"measures": []
}
******************************************************
]
}
edit
The answer below with aggregation works, but I still find it odd that I would need so much code. If after my normal query I get the same result with ".stations[0].measures", instead of the whole aggregation pipeline:
.then(stations => {
res.status(200).send(stations.stations[0].measures)
})
The way I read the code, the above does exactly the same as:
const options = {'stations.$.measures': 1}
Where the dollar sign puts in the index 0 as that was the index of the station that matches the query part: stationName: "livingroom"
Can someone explain?
This is not described in terms of mongoose but this will find a particular station name in an array of stations in 1 or more docs and return only the measures array:
db.foo.aggregate([
// First, find the docs we are looking for:
{$match: {"stations.stationName": "livingroom"}}
// Got the doc; now need to fish out ONLY the desired station. The filter will
// will return an array so use arrayElemAt 0 to extract the object at offset 0.
// Call this intermediate qqq:
,{$project: { qqq:
{$arrayElemAt: [
{ $filter: {
input: "$stations",
as: "z",
cond: { $eq: [ "$$z.stationName", "livingroom" ] }
}}, 0]
}
}}
// Lastly, just project measures and not _id from this object:
,{$project: { _id:0, measures: "$qqq.measures" }}
]);
$elemMatch operator limits the contents of an array field from the query results to contain only the first element matching the $elemMatch condition.
Try $elemMatch in Select Query as below :
const query = {
apiKey: user.apiKey,
'stations.stationName': req.params.stationName
}
const options = {
'stations' : {$elemMatch: { 'stationName' : req.params.stationName }}
}
I have two schemas called employees (parent) and assessments(child)
Every assessment will have a pass percentage of employee id
so I have results like this
employees : [
{
"_id": 12345,
"name": "David",
"evaluated": false
},
{
"_id": 12346,
"name": "Miller",
"evaluated": false
}
]
Second Schema
assessments: [
{
"assessment_type": "basic",
"employee_id": 12345,
"qualified": true
},
{
"assessment_type": "advanced",
"employee_id": 12345,
"qualified": false
},
{
"assessment_type": "basic",
"employee_id": 12346,
"qualified": true
},
{
"assessment_type": "advanced",
"employee_id": 12346,
"qualified": true
}
]
So I want to get the employees with evaluated based on assessments qualified is true
can you please tell me what is the best approach for this?
Here is an example where we sort the employees by the assements they succeeded.
const employees = [{
_id: 12345,
name: 'David',
evaluated: false,
}, {
_id: 12346,
name: 'Miller',
evaluated: false,
}];
const assessments = [{
assessment_type: 'basic',
employee_id: 12345,
qualified: true,
}, {
assessment_type: 'advanced',
employee_id: 12345,
qualified: false,
}, {
assessment_type: 'basic',
employee_id: 12346,
qualified: true,
}, {
assessment_type: 'advanced',
employee_id: 12346,
qualified: true,
}];
// Loop at the employees
const sortByAssessment = employees.reduce((tmp, x) => {
// Get all the assessment about the employee
const employeeAssessment = assessments.filter(y => y.employee_id === x._id);
// Deal with each assessment
employeeAssessment.forEach((y) => {
// Only do something about successfull assessments
if (y.qualified) {
// In case this is the first time we are dealing with the assessment_type
// create an array where we are going to insert employees informations
tmp[y.assessment_type] = tmp[y.assessment_type] || [];
// Push the name of the employee inside of the assessment type array
tmp[y.assessment_type].push(x.name);
}
});
return tmp;
}, {});
console.log(sortByAssessment);
you can do 2 things join with $look up or populate with employee id
assessments.aggregate([
{
'$lookup': {
'from': 'employees',
'localField': 'employee_id',
'foreignField': '_id',
'as': 'datas'
}
},
{ "$unwind": "$datas" },
].exec(function(err,result){
console.log(result)
});
2nd way
//assessments your model name
assessments.populate('employee_id').exec(function(err,result){
console.log(result);
});
Here is a an example document I have.
Users
{
"_id": 1,
"users": [2,3,4,5],
"scenarios": [11,22,44,55]
}
I'd like remove the elements 2 from users and 11 44 55 from scenarios. I was attemping to perform two $pull in a single update but I can't seem to get it to work with the following.
Users.update(
{ _id: 1},
{$pull: { users: 2 },
{scenarios : '11 44 55' },
function(err,numberaffected){}
);
Any help with the following query would be appreciated.
You want to pullAll
Users.update(
{ "_id": 1 },
{ "$pullAll": {"users": [2], "scenarios": [ 11, 44, 55 ]} }
,
function(err, numAffected) {
}
);
Like that, should do it.