How can I query the value of an object rather than the key value pair in MongoDB - mongodb

I've store an array on my user object which holds all of the data
{
_id: ObjectId(#############)
fname: 'Bob',
lname: 'Vargas',
data: [
// the data I want
]
}
I am using express to get his data like this:
db.users.findOne( { _id: ObjectId(#############) }, { data: 1, _id: 0 } );
but that is giving me an object rather than the array:
{ data: [ /* my data */ ]}
how can I get just the array?
UPDATE
app.get('/user/:id/data', function (req, res, next) {
db.users.findOne(
{ _id: mongojs.ObjectId(req.params.id) },
{ data: 1, _id: 0 },
function (err, userData) {
if (err) {
res.send(err);
}
res.json(userData);
}
);
});

Add projection to query result:
db.users.findOne( { _id: ObjectId(#############) }, {_id:0, data:1} )
Use 0 to exlude field from result (_id is included by default), and 1 to include field in result.
MongoDB returns object per document. But you can manually map objects on client side:
db.users.findOne( { _id: ObjectId(#############) }, {_id:0, data:1} )
.map(function(d){ return d.data; }))

MongoDB's findOne() will only return an object, not an array; thus the One. Instead you will need to receive it as and object and then get the value.
If you are doing this from mongo shell then there is no way around unless you want to move the data into its own collection. Otherwise you can get the array from the object in your application.
UPDATE:
in your express response, only encode the data value, like this
res.json(userData.data);

Related

Can't remove object in array using Mongoose

This has been extensively covered here, but none of the solutions seems to be working for me. I'm attempting to remove an object from an array using that object's id. Currently, my Schema is:
const scheduleSchema = new Schema({
//unrelated
_id: ObjectId
shifts: [
{
_id: Types.ObjectId,
name: String,
shift_start: Date,
shift_end: Date,
},
],
});
I've tried almost every variation of something like this:
.findOneAndUpdate(
{ _id: req.params.id },
{
$pull: {
shifts: { _id: new Types.ObjectId(req.params.id) },
},
}
);
Database:
Database Format
Within these variations, the usual response I've gotten has been either an empty array or null.
I was able slightly find a way around this and accomplish the deletion by utilizing the main _id of the Schema (instead of the nested one:
.findOneAndUpdate(
{ _id: <main _id> },
{ $pull: { shifts: { _id: new Types.ObjectId(<nested _id>) } } },
{ new: true }
);
But I was hoping to figure out a way to do this by just using the nested _id. Any suggestions?
The problem you are having currently is you are using the same _id.
Using mongo, update method allows three objects: query, update and options.
query object is the object into collection which will be updated.
update is the action to do into the object (add, change value...).
options different options to add.
Then, assuming you have this collection:
[
{
"_id": 1,
"shifts": [
{
"_id": 2
},
{
"_id": 3
}
]
}
]
If you try to look for a document which _id is 2, obviously response will be empty (example).
Then, if none document has been found, none document will be updated.
What happens if we look for a document using shifts._id:2?
This tells mongo "search a document where shifts field has an object with _id equals to 2". This query works ok (example) but be careful, this returns the WHOLE document, not only the array which match the _id.
This not return:
[
{
"_id": 1,
"shifts": [
{
"_id": 2
}
]
}
]
Using this query mongo returns the ENTIRE document where exists a field called shifts that contains an object with an _id with value 2. This also include the whole array.
So, with tat, you know why find object works. Now adding this to an update query you can create the query:
This one to remove all shifts._id which are equal to 2.
db.collection.update({
"shifts._id": 2
},
{
$pull: {
shifts: {
_id: 2
}
}
})
Example
Or this one to remove shifts._id if parent _id is equal to 1
db.collection.update({
"_id": 1
},
{
$pull: {
shifts: {
_id: 2
}
}
})
Example

Pulling/deleting an item from a nested array

Note: it's a Meteor project.
My schema looks like that:
{
_id: 'someid'
nlu: {
data: {
synonyms:[
{_id:'abc', value:'car', synonyms:['automobile']}
]
}
}
}
The schema is defined with simple-schema. Relevant parts:
'nlu.data.synonyms.$': Object,
'nlu.data.synonyms.$._id': {type: String, autoValue: ()=> uuidv4()},
'nlu.data.synonyms.$.value': {type:String, regEx:/.*\S.*/},
'nlu.data.synonyms.$.synonyms': {type: Array, minCount:1},
'nlu.data.synonyms.$.synonyms.$': {type:String, regEx:/.*\S.*/},
I am trying to remove {_id:'abc'}:
Projects.update({_id: 'someid'},
{$pull: {'nlu.data.synonyms' : {_id: 'abc'}}});
The query returns 1 (one doc was updated) but the item was not removed from the array. Any idea?
This is my insert query
db.test.insert({
"_id": "someid",
"nlu": {
"data": {
"synonyms": [
{
"_id": "abc"
},
{
"_id": "def"
},
10,
[ 5, { "_id": 5 } ]
]
}
}
})
And here is my update
db.test.update(
{
"_id": "someid",
"nlu.data.synonyms._id": "abc"
},
{
"$pull": {
"nlu.data.synonyms": {
"_id": "abc"
}
}
}
)
The problem broke down to the autoValue parameter on your _id property.
This is a very powerful feature to manipulate automatic values on your schema. However, it prevented from pulling as it had always returned a value, indicating that this field should be set.
In order to make it aware of the pulling, you can make it aware of an operator being present (as in cases of mongo updates).
Your autoValue would then look like:
'nlu.data.synonyms.$._id': {type: String, autoValue: function(){
if (this.operator) {
this.unset();
return;
}
return uuidv4();
}},
Edit: Note the function here being not an arrow function, otherwise it losses the context that is bound on it by SimpleSchema.
It basically only returns a new uuid4 when there is no operator present (as in insert operations). You can extend this further by the provided functionality (see the documentation) to your needs.
I just summarized my code to a reproducable example:
import uuidv4 from 'uuid/v4';
const Projects = new Mongo.Collection('PROJECTS')
const ProjectSchema ={
nlu: Object,
'nlu.data': Object,
'nlu.data.synonyms': {
type: Array,
},
'nlu.data.synonyms.$': {
type: Object,
},
'nlu.data.synonyms.$._id': {type: String, autoValue: function(){
if (this.operator) {
this.unset();
return;
}
return uuidv4();
}},
'nlu.data.synonyms.$.value': {type:String, regEx:/.*\S.*/},
'nlu.data.synonyms.$.synonyms': {type: Array, minCount:1},
'nlu.data.synonyms.$.synonyms.$': {type:String, regEx:/.*\S.*/},
};
Projects.attachSchema(ProjectSchema);
Meteor.startup(() => {
const insertId = Projects.insert({
nlu: {
data: {
synonyms:[
{value:'car', synonyms:['automobile']},
]
}
}
});
Projects.update({_id: insertId}, {$pull: {'nlu.data.synonyms' : {value: 'car'}}});
const afterUpdate = Projects.findOne(insertId);
console.log(afterUpdate, afterUpdate.nlu.data.synonyms.length); // 0
});
Optional Alternative: Normalizing Collections
However there is one additional note for optimization.
You can work around this auto-id generation issue by normalizing synonyms into an own collection, where the mongo insert provides you an id. I am not sure how unique this id will be compared to uuidv4 but i never faced id issues with that.
A setup could look like this:
const Synonyms = new Mongo.Collection('SYNONYMS');
const SynonymsSchema = {
value: {type:String, regEx:/.*\S.*/},
synonyms: {type: Array, minCount:1},
'synonyms.$': {type:String, regEx:/.*\S.*/},
};
Synonyms.attachSchema(SynonymsSchema);
const Projects = new Mongo.Collection('PROJECTS')
const ProjectSchema ={
nlu: Object,
'nlu.data': Object,
'nlu.data.synonyms': {
type: Array,
},
'nlu.data.synonyms.$': {
type: String,
},
};
Projects.attachSchema(ProjectSchema);
Meteor.startup(() => {
// just add this entry once
if (Synonyms.find().count() === 0) {
Synonyms.insert({
value: 'car',
synonyms: ['automobile']
})
}
// get the id
const carId = Synonyms.findOne()._id;
const insertId = Projects.insert({
nlu: {
data: {
synonyms:[carId] // push the _id as reference
}
}
});
// ['MG464i9PgyniuGHpn'] => reference to Synonyms document
console.log(Projects.findOne(insertId).nlu.data.synonyms);
Projects.update({_id: insertId}, {$pull: {'nlu.data.synonyms' : carId }}); // pull the reference
const afterUpdate = Projects.findOne(insertId);
console.log(afterUpdate, afterUpdate.nlu.data.synonyms.length);
});
I know this was not part of the question but I just wanted to point out that there are many benefits of normalizing complex document structures into separate collections:
no duplicate data
decouple data that is not intended to be bound (here: Synonyms could be also used independently from Projects)
update referred documents once, all Projects will point to the very actual version (since it's a reference)
finer publication/subscription handling => more control about what data flows over the wire
reduces complex auto and default value generation
changes in the referred collection's schema may have only few consequences for UI and functions that make use of the referrer's schema.
Of course this has also disadvantages:
more collections to handle
more code to write (more code = more potential errors)
more tests to write (much more time to invest)
sometimes you need to denormalize back for this one case out of 100
you have to invest a lot of time in data schema design before starting to code

Using async loop in mongodb shell for updating many documents

I have a problem with the following query in MongoDB shell ONLY when the size of the array gets bigger, for example, more than 100 elements.
newPointArray --> is an array with 500 elements
newPointArray.forEach(function(newDoc){
//update the mongodb properties for each doc
db.getCollection('me_all_test')
.update({ '_id': newDoc._id },
{ $set: { "properties": newDoc.properties } },
{ upsert: true });
})
Can someone guide me how can I run this query IN MongoDB SHELL for lager array by using an async loop or promise or...?
Thanks in advance
Rather than doing individual .update()s, use a .bulkWrite() operation. This should reduce the overhead of asking mongo to do multiple individual operations. This is assuming that you are doing general operations. I'm not clear on if newPointArray is always new points that don't exist.
Given your example, I believe your script would mimic the following:
// I'm assuming this is your array (but truncated)
let newPointArray = [
{
_id: "1",
properties: {
foo: "bar"
}
},
{
_id: "2",
properties: {
foo: "buzz"
}
}
// Whatever other points you have in your array
];
db
.getCollection("me_all_test")
.bulkWrite(newPointArray
// Map your array to a query bulkWrite understands
.map(point => {
return {
updateOne: {
filter: {
_id: point._id
},
update: {
$set: {
properties: point.properties
}
},
upsert: true
}
};
}));
You may also want to consider setting ordered to false in the operation which may also have performance gains. That would look something liked this:
db
.getCollection("me_all_test")
.bulkWrite([SOME_ARRAY_SIMILAR_TO_ABOVE_EXAMPLE], {
ordered: false
});

Unexpected result when trying to filter fields in mongo db

I have a document structured as follows:
{
_id: "someid",
games: [{
slug: "world-of-warcraft",
class: 'shaman'
}, {
slug: 'starcraft-ii',
class: 'zerg'
}],
roles: {
'starcraft-ii': ['player'],
'world-of-warcraft': ['player']
}
}
I am trying to filter it so that only starcraft-ii within the games array will show up for all players in the role of player in starcraft-ii. I do the following:
function getUsersInGame(slug) {
return Users.find({
'games.slug': slug,
[`roles.${slug}`]: 'player'
}, {
fields: {
'games.$': 1,
'roles': 1
}
});
}
However, this does not match within the games array, and instead returns a 1-element array with world-of-warcraft instead.
What is the appropriate way to filter this array in mongo?
Use $elemMatch in your fields, since the $ will return the first element of the array. So your query should look like this:
function getUsersInGame(slug) {
return Users.find(
{
'"roles.'+ slug + '"': { $in : ['player']}
},
{
'games': {
$elemMatch: {'slug': slug}
},
'roles': 1
});
Please note the difference from the docs:
"The $ operator projects the array elements based on some condition from the query statement.
The $elemMatch projection operator takes an explicit condition argument. This allows you to project based on a condition not in the query..."

Mongoose Update Instance from Nested Array

In Mongoose, lets say I have a User object pulled from MongoDB and that user has an array of Interests. Now I get an instance of one of that user's Interests.
var user = ...
var interest = ...
... //Make some changes to interest.
How do I update that Interest object (after making some changes to it) within the User array in the DB?
Edit
Here is my current code. It doesn't work and doesn't give an error.
User.update(
{
'_id': user._id,
'interests._id': interest._id
},
{
'$set': {
'interests.$.xyzProperty': interest.xyzProperty
}
},
function(err,obj){//some error checking}
);
If you set an if for each interest, you can access the interest by the $ operator.
user document
{
_id: ObectId('54b568531ef35a7c348f21f2'),
interests: [
{
_id: 12345,
title: 'Tacos',
description: 'I Love tacos'
},
{...},
{...},
]
}
If I know which interest sub document I want to update, I simply query it like so:
UserModel.find({_id: ObectId('54b568531ef35a7c348f21f2'), 'interests.i_d': 12345}).lean().exec(function (err, user) {
var interest = ... //find specific interest
interest.description = 'I love tacos... Like, a lot'.
UserModel.update(
{
_id: user._id,
'interests._id': interest._id
},
{
$set: {
'interests.$.description': interest.description
}
},
function (err, update) {
console.log(err, update);
}
);
});
This uses the $ positional operator and updates the specific sub document(or item in an array).