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"}
}
}
])
Related
I have two collections. One to store all the user Details and another to store movies. I have a user_id field which has the objectId of the user who uploads it in the movie collection.
Now I need to store all the movie ObjectId's as a array in the corresponding user collection. Like one to many relationship.
say,
I have some movie documents :
[{
'id' : '1',
'title' : 'Pk',
'user_id' : '25'
},
{
'id' : '2',
'title' : 'Master',
'user_id' : '25'
}]
In user collection, I want to store all the Movie_id's as a array to the corresponding user.
{
'user_id' : '25',
'user_name' : 'john',
'Movie_ids' : ['1','2']
}
How can I achieve this using mongodb and express.js?
Query
$lookup does what you want (see mongodb documentation is very good)
$map is used to keep only the ids in the array
Test code here
db.users.aggregate([
{
"$lookup": {
"from": "movies",
"localField": "user_id",
"foreignField": "user_id",
"as": "movies"
}
},
{
"$set": {
"movies": {
"$map": {
"input": "$movies",
"in": "$$this.id"
}
}
}
}
])
Ok, I'm not sure if this is entirely what you are looking for but you can use javascript function .filter on the movie object to get all the movies with user_id=25 and then map the id's of those movies to a new array like this:
let movies = [
{
"id": "1",
"name": "pk",
"user_id": "25"
},{
"id": "2",
"name": "Master",
"user_id": "25"
}]
let user = {
"id": "25",
"name": "john"
}
let sortedMovies = movies.filter(movie => movie.user_id === user.id).map(movie => movie.id);
user.movieIDs = sortedMovies;
A link to test the code: https://playcode.io/816962/
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"]]
I'm new to Mongodb,trying to fetch data from a collections ,firstly i need to get all the values of array "1" from the below example,without passing any value of array and secondly get data in Json format ,which will be sent to a client via cursor, toArray function works for console but not for html page.
Code:
"_id" : ObjectId("5934f65bdab27e02aa954891"),"tracker" : [
{
"1" : [
"open",
"isnot",
"closed",
"any"
],
"2" : [
"task",
"user story",
"bug",
"support",
"feature",
"ui modification",
"enhancment",
"use case"
]
}
],
"subject" : [
{
"1" : [
"contains",
"doesnot contain",
"none",
"any"
]
}
]}
Expected Output_1(values of array "1" ):
"open","isnot","closed", "any"
Expected Output_2(Json format)
"tracker" : [
{
"1" : [
"open",
"isnot",
"closed",
"any"
],
"2" : [
"task",
"user story",
"bug",
"support",
"feature",
"ui modification",
"enhancment",
"use case"
]
}],"subject" : [
{
"1" : [
"contains",
"doesnot contain",
"none",
"any"
]
}]}
db.details.aggregate(
// Pipeline
[
// Stage 1
{
$match: {"_id" : ObjectId("5934f65bdab27e02aa954891")}
},
// Stage 2
{
$unwind: "$tracker"
},
// Stage 3
{
$project: {
'tracker.1':1
}
},
]
);
Above aggregate query executes following aggregation stages sequentially in pipeline
$match operator filters document to select only document where value
of _id field is equivalent to specific Object Id
$unwind operator splits an array field into separate documents for
each value of array.
$project operator limits fields returned into result.
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.
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);
});
})