MongoDB return specific fields from array - javascript

I want to limit my query's result to a set of fields. This is one of my documents:
{
"_id" : "WA9QRuiWtGsr4amtT",
"status" : 3,
"data" : [
{
"name" : "0",
"value" : "Text ..."
},
{
"name" : "1",
"value" : "12345678"
},
{
"name" : "2",
"value" : "Text"
},
{
"name" : "4",
"value" : "2"
},
{
"name" : "8",
"value" : true
},
{
"name" : "26",
"value" : true
},
],
"userId" : "7ouEumtudgC2HX4fF",
"updatedAt" : NumberLong(1415903962863)
}
I want to limit the output to the status field as well a the first and third data document.
This is what I tried:
Meteor.publish('cases', function () {
var fields = {
currentStatus: 1,
'data.0': 1,
'data.2': 1
};
return Cases.find({}, { fields: fields });
});
Sadly it doesn't work. Something else I found is $elemMatch but it only returns the first element:
data: {
$elemMatch: {
name: {
$in: ['0', '2']
}
}
},
How can I limit the output to these fields?

To display status and data(unlimited) fields try
cases.find({}, {"status":1, "data":1})
This is simple query, to limit "data" output you will need to work harder :)
Get 1 element by data.name (not by position):
cases.find({}, {status:1, "data": {$elemMatch:{name:"0"}}})
Get 1 element by data.name, but from a list of values:
cases.find({}, {status:1, "data": {$elemMatch:{name:{$in:["0", "1"]}}}})
To get close to your question, you may try redact. That is new in Mongodb 2.6.
Or play with $unwind and .aggregate() in previous editions.
So far, I do not see a way to return array elements based on a position.

Related

How to search a key value in mongodb based on tag name in a deeply nested document?

db.props.aggregate([{"$match":{release:"1"}},{"$project":{'_id':0, 'SHK.0':{"$filter":{"input":'$SHK.0.host',"as":'fil', "cond":{$in:['$$fil.Tags',"cd"]}}}}}])
I used the above to query my dataset listed below ::
{ "_id" : ObjectId("5a0eafdf481fc70d171521b1"),
"release" : "1",
"product" : "1",
"project" : "1",
"patchset" : "1",
"common" : {
"active" : "YES",
"javahome" : "path" },
"SHK" : [
{
"host" : {
"value" : "demo",
"Tags" : [ "ci", "cd" ] },
"appserver" : {
"value" : "demo",
"Tags" : [ "ci" ] },
"appname" : {
"value" : "demo",
"Tags" : [ "cd" ] } } ] }
But the above does not seem to work I am getting a blank index ... I am trying to get here specific key value pair according to the tag name present suppose in the above query as i have mentioned cd i should get value for only host and appname and appserver should not be listed in the end result as it does not contain the tagname cd. Thanks
I think you need something like that:
db.props.aggregate([{
"$match": {
release: "1"
}
},
{
$unwind: '$SHK'
},
{
"$project": {
'_id': 0,
'tag': {
"$filter": {
"input": '$SHK.host.Tags',
"as": 'fil',
"cond": {
$in: ['$$fil', ["cd"]]
}
}
}
}
}
])
You need to $unwind the first array, in this case "SHK". After unwinding (flattening) "SHK", the only array is the "Tags" field. So then you can apply the $filter operator. Also you were missing the [] in your $in condition. You wrote:
{$in:['$$fil.Tags',"cd"]}
but $in operator is build like that:
{ $in: [ <expression>, <array expression> ] }
so in this case:
$in: ['$$fil', ["cd"]]

How to back and forth in array of data?

I have a following data which I am iterating on paragraph, which performs different task in different steps. I am not getting how to load selected view and controller in angular and make current as active.
http://prntscr.com/fisflz
[{
"0" : {
"name" : "Select Event"
},
"1" : {
"name" : "Connect"
},
"2" : {
"name" : "Configure"
},
"3" : {
"name" : "Test"
},
"4" : {
"name" : "Finish"
}
}]
Hint will appreciable.
Thanks

MongoDB query for latest data?

Pretty much my data looks something like this:
{
"name" : "Name1",
"monthson" : "4",
"data" : "OLD DATA FOR 1"
},
{
"name" : "Name1",
"monthson" : "5",
"data" : "LATEST DATA FOR 1"
},
{
"name" : "Name2",
"monthson" : "7",
"data" : "OLD DATA FOR 2"
},
{
"name" : "Name2",
"monthson" : "8",
"data" : "LATEST DATA FOR 2"
}
I'm trying to figure out a way to group everything by each name and then output the latest Data. (monthson represents how many months each set has been active so the highest monthson is the most recent).
My Mongo query looks something like this:
db.collection.aggregate(
[
{$match: {$in: ["name1", "name2"]}}
{$group:
{
_id:"$name",
monthson:{$max: "$monthson"},
data: {$addToSet: "$data"}
}},
])
The output looks like this:
{
_id:"Name1",
monthson: 5,
data: ["OLD DATA FOR 1", " LATEST DATA FOR 1"]
}
{
_id:"Name2",
monthson: 8,
data: ["LATEST DATA FOR 2", "OLD DATA FOR 2"]
}
The trick is every time I run this query it adds every set of data to my result when I only want the data that corresponds to the highest monthson. I can't query for first, last or highest data because they will always be in random order.
You can use $sort to get the order you want (I used oldest first), and then $first to get to the first (oldest) matching record for each "name":
db.collection.aggregate([
{
$match: {name: {$in: ["Name1", "Name2"]}}
},
{
$sort: {monthson:-1}
},
{
$group: {
_id:"$name",
first:{$first: "$$ROOT"}
}
}
])

Query to Match on nth Document of an Array

I am new to MongoDB and I am doing some exercises on it. In particular I got stuck on this exercise, of which I report here the question:
Given the following structure for document "Restaurant":
{
"_id" : ObjectId("5704adbc2eb7ebe23f582818"),
"address" : {
"building" : "1007",
"coord" : [
-73.856077,
40.848447
],
"street" : "Morris Park Ave",
"zipcode" : "10462"
},
"borough" : "Bronx",
"cuisine" : "Bakery",
"grades" : [
{
"date" : ISODate("2014-03-03T00:00:00Z"),
"grade" : "A",
"score" : 2
},
{
"date" : ISODate("2013-09-11T00:00:00Z"),
"grade" : "A",
"score" : 6
},
{
"date" : ISODate("2013-01-24T00:00:00Z"),
"grade" : "A",
"score" : 10
},
{
"date" : ISODate("2011-11-23T00:00:00Z"),
"grade" : "A",
"score" : 9
},
{
"date" : ISODate("2011-03-10T00:00:00Z"),
"grade" : "B",
"score" : 14
}
],
"name" : "Morris Park Bake Shop",
"restaurant_id" : "30075445"
}
Write a MongoDB query to find the restaurant Id, name and grades for those restaurants where 2nd element of grades array contains a grade of "A" and score 9 on an ISODate "2014-08-11T00:00:00Z".
I wrote this query:
db.restaurants.find(
{
'grades.1': {
'score': 'A',
'grade': 9,
'date' : ISODate("2014-08-11T00:00:00Z")
}
},
{
restaurant_id: 1,
name: 1,
grades: 1
});
which is not working.
The solution provided is the following:
db.restaurants.find(
{ "grades.1.date": ISODate("2014-08-11T00:00:00Z"),
"grades.1.grade":"A" ,
"grades.1.score" : 9
},
{"restaurant_id" : 1,"name":1,"grades":1}
);
My questions are:
is there a way to write the query avoiding to repeat the grades.1 part?
Why is my query wrong, given that grades.1 is a document object?
If it can help answering my question, I am using MongoDB shell version: 3.2.4
EDIT:
I found an answer to question 2 thanks to this question.
In particular I discovered that order matters. Indeed, if I perform the following query, I get a valid result:
db.restaurants.find({'grades.1': {'date': ISODate("2014-08-11T00:00:00Z"), 'grade':'A', score:9}}, {restaurant_id:1, name:1, grades:1})
Note that this query works only because all subdocument's "fields" are specified, and they are specified in the same order.
Not really. But perhaps an explanation of what you "can" do:
db.junk.find({
"grades": {
"$elemMatch": {
"date" : ISODate("2014-03-03T00:00:00Z"),
"grade" : "A",
"score" : 2
}
},
"$where": function() {
var grade = this.grades[0];
return (
grade.date.valueOf() == ISODate("2014-03-03T00:00:00Z").valueOf() &&
grade.grade === "A" &&
grade.score ==== 2
)
}
})
The $elemMatch allows you to shorten a little, but it is not the "nth" element of the array. In order to narrow that further you need to use the $where clause to inspect the "nth" array element to see if all values are a match.
db.junk.aggregate([
{ "$match": {
"grades": {
"$elemMatch": {
"date" : ISODate("2014-03-03T00:00:00Z"),
"grade" : "A",
"score" : 2
}
}
}},
{ "$redact": {
"$cond": {
"if": {
"$let": {
"vars": { "grade": { "$arrayElemAt": [ "$grades", 0 ] } },
"in": {
"$and": [
{ "$eq": [ "$grade.date", ISODate("2014-03-03T00:00:00Z") ] },
{ "$eq": [ "$grade.grade", "A" ] },
{ "$eq": [ "$grade.score", 2 ] }
]
}
}
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}}
])
You can do the same logic with $redact as well using .aggregate(). It runs a little quicker, but the basic truth should be clear by now.
So using "dot notation" to specify the "nth" position for each element within the array like you have already done is the most efficient and "brief" way to write this. You cannot make it shorter or better.
Your other attempt is looking for a "document" within "grades.1" that matches exactly the document condition you are providing. If for any reason those are not the only fields present, or if they are indeed in "different order" in the stored document, then such a query condition will not be a match.

MongoDB : Map Reduce : Create one sub-document from another one

I have a mongodb collection which has documents like this :
{
"_id" : ObjectId("safdsd435tdg54trgds"),
"startDate" : ISODate("2013-07-02T17:35:01.000Z"),
"endDate" : ISODate("2013-08-02T17:35:01.000Z"),
"active" : true,
"channels" : [
1, 2, 3, 4
],
}
I want to convert this to something like this :
{
"_id" : ObjectId("safdsd435tdg54trgds"),
"startDate" : ISODate("2013-07-02T17:35:01.000Z"),
"endDate" : ISODate("2013-08-02T17:35:01.000Z"),
"active" : true,
"channels" : [
1, 2, 3, 4
],
"tags" :[
{
"name": one
"type": channel
},
{
"name": two
"type": channel
},
{
"name": three
"type": channel
},
{
"name": four
"type": channel
}
]
}
I already have a mapping of what 1,2,3,4 mean. Just for the sake of simplicity I put them as their alphabetical format. the values could be different, but they're static mappings.
You seem to be trying to do this update without a big iteration of your collection, So you "could" do this with mapReduce, albeit in a very "mapReduce way" as it has it's own way of doing things.
So first you want to define a mapper that encapsulates your current document :
var mapFunction = function (){
var key = this._id;
var value = {
startDate: this.startDate,
endDate: this.endDate,
active: this.active,
channels: this.channels
};
emit( key, value );
};
Now here the reducer is actually not going to be called as all the keys from the mapper will be unique, being of course the _id values from the original document. But to make the call happy:
var reduceFunction = function(){};
As this is a one to one thing this will go to finalize. It could be in the mapper, but for cleanliness sake
var finalizeFunction = function (key, reducedValue) {
var tags = [
{ name: "one", type: "channel" },
{ name: "two", type: "channel" },
{ name: "three", type: "channel" },
{ name: "four", type: "channel" }
];
reducedValue.tags = [];
reducedValue.channels.forEach(function(channel) {
reducedValue.tags.push( tags[ channel -1 ] );
});
return reducedValue;
};
Then call the mapReduce:
db.docs.mapReduce(
mapFunction,
reduceFunction,
{
out: { replace: "newdocs" },
finalize: finalizeFunction
}
)
So that will output to a new collection, but in the way that mapReduce does it so you have this:
{
"_id" : ObjectId("53112b2d0ceb66905ae41259"),
"value" : {
"startDate" : ISODate("2013-07-02T17:35:01Z"),
"endDate" : ISODate("2013-08-02T17:35:01Z"),
"active" : true,
"channels" : [ 1, 2, 3, 4 ],
"tags" : [
{
"name" : "one",
"type" : "channel"
},
{
"name" : "two",
"type" : "channel"
},
{
"name" : "three",
"type" : "channel"
},
{
"name" : "four",
"type" : "channel"
}
]
}
}
So all your document fields other than _id are stuck under that value field, so that's not the document that you want. But that is how mapReduce works.
If you really need to get out of jail from this and are willing to wait a bit, the upcoming 2.6 release has added an $out pipeline stage. So you "could" transform the documents in your new collection with $project like this:
db.newdocs.aggregate([
// Transform the document
{"$project": {
"startDate": "$value.startDate",
"endDate": "$value.endDate",
"active": "$value.active",
"channels": "$value.channels",
"tags": "$value.tags"
}},
// Output to new collection
{"$out": "fixeddocs" }
])
So that will be right. But of course this is not your original collection. So to back to that state you are going to have to .drop() collections and use .renameCollection() :
db.newdocs.drop();
db.docs.drop();
db.fixeddocs.renameCollection("docs");
Now please READ the documentation carefully on this, there are several limitations, and of course you would have to re-create indexes as well.
All of this, and in particular the last stage is going to result in a lot of disk thrashing and also keep in mind that you are dropping collections here. It almost certainly is a case for taking access to your database off-line while this is performed.
And even as such the dangers here are real enough that perhaps you can just live with running an iterative loop to update the documents, using arbitrary JavaScript. And if you really must have to do so, you could always do that using db.eval() to have that all execute on the server. But if you do, then please read the documentation for that very carefully as well.
But for completeness even if I'm not advocating this:
db.eval(function(){
db.docs.find().forEach(function(document) {
var tags = [
{ name: "one", type: "channel" },
{ name: "two", type: "channel" },
{ name: "three", type: "channel" },
{ name: "four", type: "channel" }
];
document.tags = [];
document.channels.forEach(function(channel) {
document.tags.push( tags[ channel -1 ] );
});
var id = document._id;
delete document._id;
db.docs.update({ "_id": id },document);
});
})

Categories

Resources