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')
Related
I'm trying to write a way to update a whole MongoDB document including subdocuments using Mongoose and a supplied update object. I want to supply an object from my client in the shape of the schema and then iterate over it to update each property in the document including those in nested subdocuments in arrays.
So if my schema looked like this:
const Person = new Schema({
name: String,
age: Number,
addresses: [
{
label: String,
fullAddress: String,
},
],
bodyMeasurements: {
height: Number,
weight: Number,
clothingSizes: {
jacket: Number,
pants: Number
},
},
})
I would want to supply an object to update an existing document that looked something like this:
{
"_id": "217a7f84685f49642635dff0",
"name": "Dan",
"addresses": [
{
"_id": "5f49647f84f02635df217a68",
"label": "Home 2"
},
{
"label": "Work",
"fullAddress": "6 Elm Street"
}
],
"bodyMeasurements": {
"_id": "2635df217a685f49647f84f0",
"weight": 90,
"clothingSizes": {
"_id": "217a685f4962635df49647f84f0",
"pants": 32
}
}
}
The code would need to iterate through all the keys and values of the object entries, and where it found a Mongo ID it would know to update that specific item (like the "Home 2" address label), and where it didn't it would know to add it (like the second "Work" address here), or replace it if it was a property on the top level. (like the "name" property with "Dan")
For a one dimensional Schema without taking into account _id's this would work:
for (const [key, value] of Object.entries(req.body)) {
person[key] = value;
}
But not for any nested subdocuments or documents in arrays. I don't want to have to specify the name of the subdocuments for each case. I am trying to find a way to update any Schema's documents and subdocuments generically.
I imagine there might be some recursion and the Mongoose .set() method needed to handle deeply nested documents.
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.
arr=[{field1:<value1>,field2:<value2}.....]
I want to use the $in operator against the field1 of arr. I know I could create a dummy array and push the field1 values in the dummy array. But is there a way to use the $in operator against a particular field of an array?
The arr array is no way related to the collection.
I want to query all the documents on a particular field whose value is in the field1 of arr - field1 should be in the right hand side of operator $in
Example:
arr=[{name:'foo',location:'NY'},{name:'bar',location:'LA'},{name:'foobar',location:'NZ'}]
db.collection.find({fieldx:<Here I want some method to obtain all documents whose fieldx values are in the location field of arr>})
The output should contain documents whose fieldx values are present in the location field of arr.
The ouput of the query should be
[{...
fieldx:'NY',
...
},
{...
fieldx:'LA',
...
},
{...
fieldx:'NZ',
...
}]
fieldx is the field in the collection I am querying on. It is not a field of the array I'm providing(arr). I want to match this to a field(location) of array - arr I'm providing the query.
You need to extract the "location" fields from your input array and feed them to $in:
var locs = arr.map(function(x) { return x.location } );
db.collection.find({ "fieldx": { "$in": locs } })
For reference here I'm going to re-write your question for you:
I have a collection that contains documents like this:
{ "fieldx": "NY" }
{ "fieldx": "LA" }
{ "fieldx": "SF" }
What I have is an input array that is defined like this:
var arr = [
{ "name": "foo", "location": "NY"},
{ "name": "bar", "location": "LA"},
{ "name": "foobar", "location": "NZ"}
];
Now I want to write a query to find all the documents that match the "location" field in the array I have for input.
How do I do I do that?
I have tried:
db.collection.find({ "fieldx": { "$in": arr } })
But that does not match.
Extending Neil Lunn's answers, you can use map function within query also.
var arr = [
{ "name": "foo", "location": "NY"},
{ "name": "bar", "location": "LA"},
{ "name": "foobar", "location": "NZ"}
];
db.collection.find({ "fieldx": { "$in": arr.map(function(x) { return x.location } ) } })
If you using ES6 syntax then
db.collection.find({ "fieldx": { "$in": arr.map(x => x.location ) } })
You need to use dot notation to select fields in the array:
db.coll.find({"arr.field1" : { $in : [ 'value1', 'value11' ]}});
This query will return all documents where array arr contains at least one subdocument that has a field field1 with values value1 or value11.
Edit
Regarding your edit, you can't use $in operator that way.
From the documentation:
The $in operator selects the documents where the value of a field
equals any value in the specified array.
If you send an array of objects in the $in query it will match document where specified field contains that object:
db.coll.find({ a: { $in : [ { b : 1, c: 1}, { b : 2, c: 2} ]}});
Will find this documents:
{
_id : ...
a: { b : 1, c: 1}
}
{
_id : ...
a: { b : 2, c: 2}
}
To get what you really want, easiest way to query is to extract the values from your array of objects and do a query with the created array of values.
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.
I am looking for a quick way to crate a key values pair in JavaScript, where I have a key it's an ID, like 1, 2, 3, 4, 5, .... 100 and the values are an array composed by a reference, name and quantity.
Is this an appropriate syntax?
var petfoodRefData = {
"1": [
{"ref":5222, "description":"purina", "qtt":500},
{"ref":5322, "description":"hills", "qtt":500},
{"ref":6222, "description":"hills junior", "qtt":500}
],
"2": {"ref":8022, "description":"fatcat", "qtt":60}
}
Is there an easier better way to do this? I basically want to for every ID = 1 give me all pet food references, and add them to the document.
You're close:
var petfoodRefData = {
"1": [
{"ref":5222, "description":"purina", "qtt":500},
{"ref":5322, "description":"hills", "qtt":500},
{"ref":6222, "description":"hills junior", "qtt":500}
],
"2": [{ "ref":8022, "description":"fatcat", "qtt":60} ]
};
Use [] for arrays.