Using $pull in Mongodb to remove a deeply embedded object - javascript

I'm trying to delete ($pull) an object from an array that's embedded. (Using javascript/node.js driver.)
Here is the sample data, where one, two, three are the levels:
{
one : "All",
one : [
{
name: "People",
two: [
{
three_id: 123,
three: "Jonny",
},
{
three_id: 456,
three: "Bobby",
}
]
},
{
name: "Animals",
two: [
{
three_id: 828,
three: "Cat",
},
{
three_id: 282,
three: "Dog",
}
]
}
]
}
In this example, I'm trying get rid of "Bobby".
I can successfully match the document at the "three level" if I want, like this:
db.test.find({"one.two.three_id" : 456});
However, I've no idea how to eliminate that record using update. Here are some attempts, none of which work:
// failed attempts
db.test.update({"one.two.three_id" : 456}, {$pull:{'one.$.two.$.three_id': 456}});
db.test.update({"one.two.three_id" : 456}, {$pull:{'three_id': 456}});
// deletes entire level two "People"
db.test.update({"one.two.three_id" : 456}, {$pull: {one: {two : {$elemMatch: {'three_id': 456}}}}});
I read that you cannot use two positional $ operators and that you have to know the index position for the second one. However, I want to avoid having to use the index of the embedded dictionary I want to delete.
reference:
Mongodb on pull
http://docs.mongodb.org/manual/reference/operator/update/pull/

The value of the key in your $pull object needs to be the path of the array that you're targeting. This appears to work:
db.test.update(
{'one.two.three_id': 456},
{$pull: {'one.$.two': {three_id: 456}}}
);
It looks like the $ represents the index of the first matched array level in this case so it works even though we're matching across multiple nesting levels.

Related

Select Matching Array Element and Return Selected Fields

I would like to know how to set the projection for a matched array of objects from a Mongoose query.
For example, if I have a Mongoose model that looks something like this:
var User = new Schema({
name: String,
children: [Child]
});
var Child = new Schema({
name: String,
height: Number,
age: Number,
secret: Number
});
In other words, an example JSON object that might result from this model:
User: {
name: 'abc',
children: [
{
name: 'def',
height: 123,
age: 7,
secret: 2
},
{
name: 'ghi',
height: 456,
age: 9,
secret: 3
}
]
}
As you can see the model contains a property children that is an array of Child objects.
If I match only User that contain an item in children that has property name: 'def':
Users.find({
children.name: 'def'
})
I can then set the projection to select properties (such as name) and also properties from the matched object using a positional operator ($):
.select({
name: 1,
children.$: 1
}
The problem now is that with this projection, children.$ will always return the entire Child object, including properties I may not want to query, such as secret.
{
name: 'abc',
children: [
{
name: 'def',
height: 123,
age: 7,
secret: 2
}
]
}
Ideally I would like to be able to also select certain properties from the child object obtained through $ similar to how name was selected from the parent object User, but I cannot find a way to do this.
One way to select a single property is to use the format children.$.age but this can only be used to select 1 property, as doing it multiple times results in an error as you cannot use the poisitional $ operator multiple times.
.select({
name: 1,
// and now select the height and age
// but only of the child that matches name = 'def'
// WITHOUT returning the entire object (exclude name and secret)
children.$.age,
children.$.height // error
})
Is selecting the projection for an object obtained by the positional operator possible in Mongoose?
If you want to only select certain fields of an array to return then you are talking about "reshaping" the document. For anything beyond "basic" field selection, this means using .aggregate() as the method instead of .find().
So the two requirements here are to $filter on the array content to "match" and return, as well as $map the actual "fields to return" from the array itself:
User.aggregate([
{ "$match": { "children.name": "def" } },
{ "$project": {
"name": 1,
"children": {
"$map": {
"input": {
"$filter": {
"input": "$children",
"as": "c",
"cond": { "$eq": [ "$$c.name", "def" ] }
}
},
"as": "c",
"in": {
"age": "$$c.age",
"height": "$$c.height"
}
}
}
}}
])
Here $filter is used in order to reduce the contents of the array down to only those that match the condition. Being those that have the same "name" property as the value "def". This is then passed as the "input" parameter to
$map.
The $map operator works just like it's other language counterparts in that it "reshapes arrays" to return something according to what you specify in the "in" parameter. So here we actually only explicitly name the properties and use there variable assignments for the current array element being processed so that these are what are returned as the "new" array content.
The overall result is an array, containing:
Just the items matching the conditions specified.
Just the fields that were specified to return.

Loop through an array of objects and get where object.field equals value

I'm currently working on a small application where I have to loop through an enormous array of objects. What would be the most efficient method to perform this?
var array = [
{
id: "1",
name: "Alpha"
},
{
id: "2",
name: "Beta"
},
...
];
I'd like to get each object where name equals "Alpha". I'm currently using a simple if statement to filter the objects with a different name value out, but I wonder if there's a more efficient way to do this, performance-wise.
It's worth to mention that I'll push the matching results into a new array.
No, there is no more efficient way.
The alternative is to build and maintain some kind of internal data structure which allows you to find the desired elements faster. As usual, the trade off is between the work involved in maintaining such a structure vs the time it saves you.
I don't have any way about which I would know it's more effective.
But if you had your objects ordered by name you could stop your search imideatly upon reaching an object whose name is not equal to "Alpha".
To find the first object you're looking for you can use binary search and from this Object you go up and down until at both ends you reach an object which isn't named "Alpha" or the end of array.
This is only a way of optimizing and will require time to sort the array and also will take more time when adding an element.
There's a JavaScript function exactly for this kind of task. Filter
From the Docs
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
Here is a small example by code for getting all element from array which has a certain 'name' field:
const arr = [
{name: 'Abc'},
{name: 'Xyz'},
{name: 'Lmn'},
{name: 'Xyz'},
{name: 'Xyz'}
];
let response = findByName('Xyz');
console.log(response);
function findByName (name) {
return arr.filter((element) => {
return element.name = name;
});
}
If you need more than one time a collection with a given name, you could use an object with the names as hashes and have instantly access to the items.
var array = [{ id: "1", name: "Alpha" }, { id: "2", name: "Beta" }, { id: "3", name: "Beta" }, { id: "4", name: "Gamma" }, { id: "5", name: "Beta" }, { id: "2", name: "Alpha" }],
hash = Object.create(null);
array.forEach(function (a) {
if (!hash[a.name]) {
hash[a.name] = [];
}
hash[a.name].push(a);
});
console.log(hash);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to merge a heterogeneous array to a single document in MongoDb?

The MongoDb of my website stores a single document for each user. Each user will answer a couple of questionnaire forms during his visit. The forms are stored in an array, but since the documents don't overlap, a flat, single document would suffice. For analysis, I wish to produce a flat table of all the answers over all the forms.
Consider the following data structure:
{
"USER_SESSION_ID": 456,
"forms": [
{
"age": 21,
"gender": "m"
},
{
"job": "Student",
"years_on_job": "12"
},
{
"Hobby": "Hiking",
"Twitter": "#my_account"
}
]
},
{
"USER_SESSION_ID": 678,
"forms": [
{
"age": 46,
"gender": "f"
},
{
"job": "Bodyguard",
"years_on_job": "2"
},
{
"Hobby": "Skiing",
"Twitter": "#bodyguard"
}
]
}
The form-documents all look different and have no conflicting fields, so I would like to merge them, yielding a tabular, flat structure like this:
{ 'USER_SESSION_ID': 456, 'age': 21, 'gender': 'm', 'job': 'Student', ... 'Twitter': '#my_account' }
{ 'USER_SESSION_ID': 678, 'age': 46, 'gender': 'f', 'job': 'Bodyguard', ... 'Twitter': '#bodyguard' }
Using Python, this is a total no-brainer, looking like this:
for session in sessions: # Iterate all docs
for form in session['forms']: # Iterate all children
session.update(form) # Integrate to parent doc
del session['forms'] # Remove nested child
In MongoDb I find this quite hard to achieve. I am trying to use the aggregate pipeline, which I imagine should be suitable for this.
So far I helped myself by unwinding my datastructure, like this:
db.sessions.aggregate(
{
'$unwind': '$forms'
},
{
'$project': {
'USER_SESSION_ID': true,
'forms': true
}
},
{
'$group': {
'_id': '$USER_SESSION_ID',
'forms': <magic?!>
}
}
)
In the unwinding stage, I create a document with the parent's data for each child. This should be roughly equivalent to the double-for loop in my python code. However what I feel like I'm conceptually missing is the "Merge" accumulator upon grouping. In python, this is done with dict.update(), in underscore.js it would be _.extend(destination, *sources).
How do I achieve this within MongoDB?
Try the following which uses nested forEach() method calls of the find() cursor to iterate over the cursor result and get the object keys for the elements within the forms array using Object.keys():
db.sessions.find().forEach(function (doc){
doc.forms.forEach(function (e){
var keys = Object.keys(e);
keys.forEach(function(key){ doc[key] = e[key] });
});
delete doc.forms;
db.sessions.save(doc);
});
I played around with the aggregate pipeline for ages until I gave the mapReduce command a try. This is what I came up with:
db.sessions.mapReduce(
function () {
var merged = {};
this.forms.forEach(function (form) {
for(var key in form) {
merged[key] = form[key];
}
});
emit(this.USER_SESSION_ID, merged);
},
function () {},
{
"out": {"inline": true}
}
)
The mapping step combines the elements, since there is no single $merging operator available as an aggregation pipeline step. The empty reduce function is required. The out either writes to a different collection or just returns the result (inline, what I'm doing here).
It looks a lot like the method that chridam showed in his answer, but actually uses a projection. His version is much closer to the way that my python code works, but for what I'm trying to do a projection is fine and doesn't change the original set. Note that the python code does that, but not chaning the input collection is quite useful!

How to Peels off the elements of an array individually in rethinkdb?

I have documents like this:
{
id:1,
"A": [
{
"C": "abc",
"D": [{X:"test"},{X:"test2"}]
},
{
"C": "fg",
"D":["X1"]
}
]
}
How to get all id of document whose tag match A-> D -> X has value "test". I can use concatMap() but when I use it I am not able get "id" field and also cannot use it inside map, is there any similar features like $unwind of Mongodb's aggregation framework?
Similar to :Querying array of nested objects
[Original Question]
{
id:2,
tags[{a:3,b:4},..]
}
Your originial question had this object:
{ id: 2,
tags: [ { a: 3, b: 4 }, ... ] }
You can construct a predicate that finds the relevant documents, and pass it to filter.
r.table(...).filter(r.row('tags')('a').contains(3))('id')
In this case, the ('a') part of the query, when applied to an array, returns an array of the a field of each object in that array, if there is one.
Your edited question has a more complicated object, but the principle is the same:
r.table(...).filter(
r.row('A')('D').concatMap(function(x){return x})('X').contains("test")
)('id')

Mongo: cross referencing values in other objects

I have a collection that looks like the ff:
{
"word":"approve",
"related" : [
{
"relationshipType" : "cross-reference",
"words" : [
"note"
]
},
{
"relationshipType" : "synonym",
"words" : [
"demonstrate",
"ratify",
]
}
],
},
{
"word": "note",
"related" : [
{
"relationshipType" : "synonym",
"words" : [
"butt",
"need",
],
},
{
"relationshipType" : "hypernym",
"words" : [
"air",
"tone",
]
},
{
"relationshipType" : "cross-reference",
"words" : [
"sign",
"letter",
"distinction",
"notice",
]
},
],
}
I want to group/categorize all the objects which have a word in another object, be it
the name (as the cross-reference field of 'approve', has note. searches for the word 'note'.
or
the word is in another object's related words. like having a synonym of 'ratify' under 'approve' then looking for other objects that have have 'ratify' in any field of their related words.
Then save these to a new collection called categories.
result should Be:
{
"category": 1,
"words":["approve","note"],
}
...and the value of the word field for all the linked objects in the words array.
Any way how to do this.. i'm thinking about some sort of recursion in checking links but i'm not sure how to implement. another potential problem is going back to the parent layer creating an infinite loop of sorts.
and is it possible through map reduce?
EDIT: clarity.

Categories