MongoDB: Return objects in a nested array that has the most recent date - mongodb

I have the following collection:
{
"_id" : ObjectId("56f036e032ea1f27634e2f1f"),
"mockups" : [
{
"versions" : [
{
"title" : "About us 1",
"timestamp" : "2016-01-10T12:31:23.104Z",
"_id" : ObjectId("56ec65a9041c87dd6bd17922")
},
{
"title" : "About us 3",
"timestamp" : "2016-03-11T15:34:11.108Z",
"_id" : ObjectId("56ec65a9041c87dd6bd17923")
},
{
"title" : "About us 2",
"timestamp" : "2016-02-21T16:15:23.101Z",
"_id" : ObjectId("56ec65a9041c87dd6bd17924")
}
],
"_id" : ObjectId("56ec65a9041c87dd6bd17921")
},
{
"versions" : [
{
"title" : "Contact us 1",
"timestamp" : "2016-04-10T11:34:33.103Z",
"_id" : ObjectId("56ec65a9041c87dd6bd17924")
},
{
"title" : "Contact us 3",
"timestamp" : "2016-06-21T16:13:26.101Z",
"_id" : ObjectId("56ec65a9041c87dd6bd17926")
},
{
"title" : "Contact us 2",
"timestamp" : "2016-05-11T13:34:13.106Z",
"_id" : ObjectId("56ec65a9041c87dd6bd17925")
}
],
"_id" : ObjectId("56ec65a9041c87dd6bd17929")
}
]
}
I want to return all mockups with the latest version that would result in something similar to the following:
{
"_id" : ObjectId("56f036e032ea1f27634e2f1f"),
"mockups" : [
{
"versions" : [
{
"title" : "About us 3",
"timestamp" : "2016-03-11T15:34:11.108Z",
"_id" : ObjectId("56ec65a9041c87dd6bd17923")
}
],
"_id" : ObjectId("56ec65a9041c87dd6bd17921")
},
{
"versions" : [
{
"title" : "Contact us 3",
"timestamp" : "2016-06-21T16:13:26.101Z",
"_id" : ObjectId("56ec65a9041c87dd6bd17926")
}
],
"_id" : ObjectId("56ec65a9041c87dd6bd17929")
}
]
}
I have been playing with aggregate, sort, and limit, but I am extremely new to mongodb. I am currently left with having to just return everything and using something like lodash to get the version I need.
Is there a way to query this properly so that I am getting the results I need from the mongo as opposed to using something like lodash after I get the results back?

For a solution using aggregation, would suggest running the following pipeline to get the desired output:
db.collection.aggregate([
{ "$unwind": "$mockups" },
{ "$unwind": "$mockups.versions" },
{ "$sort": { "mockups.versions.timestamp": -1 } },
{
"$group": {
"_id": "$mockups._id",
"title": { "$first": "$mockups.versions.title" },
"timestamp": { "$first": "$mockups.versions.timestamp" },
"id" : { "$first": "$mockups.versions._id" },
"main_id": { "$first": "$_id" }
}
},
{
"$group": {
"_id": "$_id",
"versions": {
"$push": {
"title": "$title",
"timestamp": "$timestamp",
"_id": "$id"
}
},
"main_id": { "$first": "$main_id" }
}
},
{
"$group": {
"_id": "$main_id",
"mockups": {
"$push": {
"versions": "$versions",
"_id": "$_id"
}
}
}
}
])

I didn't actually understood what is the range for "latest" version ,however I think you can use some thing like this:
example:
db.collection.find({
versions.timestamp: {
$gte: ISODate("2010-04-29T00:00:00.000Z"),
$lt: ISODate("2010-05-01T00:00:00.000Z")
}
})
if you want to use the aggregate you can put this condition in the $match .

Related

How to $lookup in nested array of objects

I have to two collections one is tours and other is destinations so in tours have i have an array of locations which has destination object with an id and that id is belongs to another destinations collection but the thing is i am not be able to lookup details of destination in the array of locations. every tried many query search here too. but not getting expected result.
Tours :
{
"_id" : ObjectId("5f3122f4d8d57e3b9650e5b4"),
"title" : "tour 1",
"locations" : [
{
"destination" : {
"id" : "5ec5ae9037ea99f20a79071a"
},
"services" : {
"hotel" : true
}
},
{
"destination" : {
"id" : "5ec5ae8e37ea99f20a78ef8c"
},
"services" : {
"hotel" : true
}
}
]
}
{
"_id" : ObjectId("5f2d65e68bc6e9155310d147"),
"title" : "tour 2",
"locations" : [
{
"destination" : {
"id" : "5ecf994435c3a6025d5bf126"
},
"services" : {
"hotel" : true
}
}
]
}
{
"_id" : ObjectId("5f2d66398bc6e9155310d161"),
"title" : "tour 3",
"locations" : [
{
"destination" : {
"id" : "5ec5ae8e37ea99f20a78ef8d"
},
"services" : {
"hotel" : true
}
}
]
}
Destinations :
{
"_id" : ObjectId("5ec5ae9037ea99f20a79071a"),
"name" : "dest 1",
"country" : "country name"
}
{
"_id" : ObjectId("5ec5ae8e37ea99f20a78ef8c"),
"name" : "dest 2",
"country" : "country name"
}
{
"_id" : ObjectId("5ec5ae8e37ea99f20a78ef8d"),
"name" : "dest 3",
"country" : "country name"
}
{
"_id" : ObjectId("5ecf994435c3a6025d5bf126"),
"name" : "dest 4",
"country" : "country name"
}
Expected result :
{
"_id" : ObjectId("5f3122f4d8d57e3b9650e5b4"),
"title" : "tour 1",
"locations" : [
{
"destination" : {
"id" : "5ec5ae9037ea99f20a79071a",
"name" : "dest 1",
"country" : "country name"
},
"services" : {
"hotel" : true
}
},
{
"destination" : {
"id" : "5ec5ae8e37ea99f20a78ef8c",
"name" : "dest 2",
"country" : "country name"
},
"services" : {
"hotel" : true
}
}
]
},
{
"_id" : ObjectId("5f2d65e68bc6e9155310d147"),
"title" : "tour 2",
"locations" : [
{
"destination" : {
"id" : "5ecf994435c3a6025d5bf126",
"name" : "dest 4",
"country" : "country name"
},
"services" : {
"hotel" : true
}
}
]
},
{
"_id" : ObjectId("5f2d66398bc6e9155310d161"),
"title" : "tour 3",
"locations" : [
{
"destination" : {
"id" : "5ec5ae8e37ea99f20a78ef8d",
"name" : "dest 3",
"country" : "country name"
},
"services" : {
"hotel" : true
}
}
]
}
Tried query :
db.tours.aggregate([
{
"$addFields": {
"locations": {
"$map": {
"input": "$locations",
"in": {
"$mergeObjects": [
"$$this",
{
"dest_oid": {
"$toObjectId": "$$this.destination.id"
}
}
]
}
}
}
}
},
{ "$unwind": "$locations" },
{ "$lookup": {
"from": "destinations",
"localField": "locations.dest_oid",
"foreignField": "_id",
"as": "locations.dest",
}},
{ "$unwind": "$locations.dest" },
{ "$group": {
"_id": "$_id",
"locations": { "$push": "$locations" }
}}
])
even i have tried this
MongoDB $lookup on nested document
Quick fixes,
$unwind locations array put first because need to convert id to object id
db.tours.aggregate([
{ $unwind: "$locations" },
you skip this part if you have already converted string id t object id
$addFields replace locations.destination.id to object id filtered your logic to short, here no need $map and $mergeObjects options
{
$addFields: {
"locations.destination.id": {
$toObjectId: "$locations.destination.id"
}
}
},
$lookup that you have already did, but change as locations.destination
{
$lookup: {
from: "destinations",
as: "locations.destination",
localField: "locations.destination.id",
foreignField: "_id"
}
},
$unwind locations.destination because its array and we need object
{
$unwind: {
path: "$locations.destination"
}
},
$group that you have already did, few changes, push first destination and services in locations and add first title
{
$group: {
_id: "$_id",
locations: { $push: "$locations" },
title: { $first: "$title" }
}
}
])
Playground: https://mongoplayground.net/p/yaTCij7NRUj
If you can convert/update the string destination.id in the Tours collection to ObjectId if they are not already. Following query should work.
Query:
db.Tours.aggregate([
{
$unwind: "$locations",
},
{
$lookup: {
from: "Destinations",
localField: "locations.destination.id",
foreignField: "_id",
as: "destination",
},
},
{
$project: {
title: "$title",
locations: {
destination: {
$arrayElemAt: ["$destination", 0],
},
services: "$locations.services",
},
},
},
{
$group: {
_id: "$_id",
title: {
$first: "$title",
},
locations: {
$push: "$locations",
},
},
},
]);
Playground Link

Issue retrieving subdocuments from MongoDB

I have the following dataset:
{
"_id" : ObjectId("59668a22734d1d48cf34de08"),
"name" : "Nobody Cares",
"menus" : [
{
"_id" : "menu_123",
"name" : "Weekend Menu",
"description" : "A menu for the weekend",
"groups" : [
{
"name" : "Spirits",
"has_mixers" : true,
"sizes" : [
"Single",
"Double"
],
"categories" : [
{
"name" : "Vodka",
"description" : "Maybe not necessary?",
"drinks" : [
{
"_id" : "drink_123",
"name" : "Absolut",
"description" : "Fancy ass vodka",
"sizes" : [
{
"_id" : "size_123",
"size" : "Single",
"price" : 300
}
]
}
]
}
]
}
],
"mixers" : [
{
"_id" : "mixer_1",
"name" : "Coca Cola",
"price" : 150
},
{
"_id" : "mixer_2",
"name" : "Lemonade",
"price" : 120
}
]
}
]
}
And I'm attempting to retrieve a single drink from that dataset, I'm using the following aggregate query:
db.getCollection('places').aggregate([
{ $match : {"menus.groups.categories.drinks._id" : "drink_123"} },
{ $unwind: "$menus" },
{ $project: { "_id": 1, "menus": { "groups": { "categories": { "drinks": { "name": 1 } } } } } }
])
However, it's returning the full structure of the dataset along with the correct data.
So instead of:
{
"_id": "drink_123",
"name": "Absolut"
}
I get:
{
"_id": ObjectId("59668a22734d1d48cf34de08"),
"menus": {
"groups": {
"categories": {
"drinks": { "name": "Absolut" }
}
}
}
}
For example. Any ideas how to just retrieve the subdocument?
If you need to retain the deeply nested model then this call will produce the desired output:
db.getCollection('places').aggregate([
{ $match : {"menus.groups.categories.drinks._id" : "drink_123"} },
{ $project: {"_id": '$menus.groups.categories.drinks._id', name: '$menus.groups.categories.drinks.name'}},
{ $unwind: "$name" },
{ $unwind: "$name" },
{ $unwind: "$name" },
{ $unwind: "$name" },
{ $unwind: "$_id" },
{ $unwind: "$_id" },
{ $unwind: "$_id" },
{ $unwind: "$_id" }
])
The numerous unwinds are the result of the deep nesting of the drinks subdocuments.
Though, FWIW, this sort of query does perhaps suggest that the model isn't 'read friendly'.

Getting data from subdocument between dates

I'm trying to get a certain amount of historical data from my documents similar to the SQL's Between clause. I searched for similar questions and managed to find this query but it only returns the first element of the array, so it's not enough:
db.docs.find({ _id: "eraj4983tj3" }, {history: { $elemMatch : {time: {$gt: new ISODate("2016-03-21T20:53:33.662Z") , $lt: new ISODate("2016-03-21T20:54:20.313Z") } } } });
My documents look like this:
{
"_id": "eraj4983tj3",
"descr": "somestuff",
"history": [
{
"time": ISODate("2016-03-21T20:52:31.389Z"),
"value": 103.91
},
{
"time": ISODate("2016-03-21T20:53:33.663Z"),
"value": 106.91
},
{
"time": ISODate("2016-03-21T20:53:45.179Z"),
"value": 104.91
},
{
"time": ISODate("2016-03-21T20:54:20.313Z"),
"value": 105.11
},
{
"time": ISODate("2016-03-21T20:54:53.649Z"),
"value": 105.41
},
{
"time": ISODate("2016-03-21T20:55:12.998Z"),
"value": 115.91
}
]
}
And the result of my query should return this:
{
"_id": "eraj4983tj3",
"history": [
{
"time": ISODate("2016-03-21T20:53:45.179Z"),
"value": 104.91
},
{
"time": ISODate("2016-03-21T20:54:20.313Z"),
"value": 105.11
},
{
"time": ISODate("2016-03-21T20:54:53.649Z"),
"value": 105.41
}
]
}
How should i write my query to achieve this result?
What you want is not possible with a simple query. However, you can use an aggregation:
db.yourColl.aggregate([
{ $match:{ _id:"eraj4983tj3" } },
{ $unwind: "$history" },
{ $match:{ "history.time":{
$gt: new ISODate("2016-03-21T20:53:33.662Z") ,
$lt: new ISODate("2016-03-21T20:54:20.313Z")
}}},
{ $group:{ _id:"$_id", history:{ $push:"$history" }}}
])
Which gives you the (correct) result of
{
"_id" : "eraj4983tj3",
"history" : [
{ "time" : ISODate("2016-03-21T20:53:33.663Z"), "value" : 106.91 },
{ "time" : ISODate("2016-03-21T20:53:45.179Z"), "value" : 104.91 }
]
}
Now, let us examine the stages:
{ $match:{ _id:"eraj4983tj3" } }: Find the correct doc
{ $unwind: "$history" } Create a new document for each array item and send it down the pipeline.
The output of this stage looks like this:
{ "_id" : "eraj4983tj3", "descr" : "somestuff", "history" : { "time" : ISODate("2016-03-21T20:52:31.389Z"), "value" : 103.91 } }
{ "_id" : "eraj4983tj3", "descr" : "somestuff", "history" : { "time" : ISODate("2016-03-21T20:53:33.663Z"), "value" : 106.91 } }
{ "_id" : "eraj4983tj3", "descr" : "somestuff", "history" : { "time" : ISODate("2016-03-21T20:53:45.179Z"), "value" : 104.91 } }
{ "_id" : "eraj4983tj3", "descr" : "somestuff", "history" : { "time" : ISODate("2016-03-21T20:54:20.313Z"), "value" : 105.11 } }
{ "_id" : "eraj4983tj3", "descr" : "somestuff", "history" : { "time" : ISODate("2016-03-21T20:54:53.649Z"), "value" : 105.41 } }
{ "_id" : "eraj4983tj3", "descr" : "somestuff", "history" : { "time" : ISODate("2016-03-21T20:55:12.998Z"), "value" : 115.91 } }
{ $match:{ "history.time":{ $gt: new ISODate("2016-03-21T20:53:33.662Z"), $lt: new ISODate("2016-03-21T20:54:20.313Z")}}} We match again to find the history items from above stage that match our date range. The output of this stage looks like this:
{ "_id" : "eraj4983tj3", "descr" : "somestuff", "history" : { "time" : ISODate("2016-03-21T20:53:33.663Z"), "value" : 106.91 } }
{ "_id" : "eraj4983tj3", "descr" : "somestuff", "history" : { "time" : ISODate("2016-03-21T20:53:45.179Z"), "value" : 104.91 } }
{ $group:{ _id:"$_id", history:{ $push:"$history" }}} We group by _id (nothing new here), and for each document of the output of above stage, we $push its value for history (an object) to the array history in the newly created grouped document.
The positional operator $ and the elemMatch will only return one sub document.

how to use the aggregation of mongodb with meteor

i'm using Meteorjs and mongo
I have a collection structure like this:
{
"_id" : "LxMaZhRX7ZbAkiFqF",
"categorie" : "International matches",
"division" : [
{
"nameDivision" : "div 1",
"type" : "Championat"
},
{
"nameDivision" : "div 2",
"type" : "Coupe"
},
{
"nameDivision" : "div 3",
"type" : "Championat"
},
{
"nameDivision" : "div 4",
"type" : "Championat"
}
]
}
I want to have this result in my subscription:
{
"_id" : "LxMaZhRX7ZbAkiFqF",
"categorie" : "International matches",
"division" : [
{
"type" : "Championat",
division:[{
"nameDivision" : "div 1",
"nameDivision" : "div 3",
"nameDivision" : "div 4",
}]
},
{
"type" : "Coupe",
"division":[{
"nameDivision" : "div 2",
}]
},
{
]
}
I need your help on how to use the function with mongodb aggegate to group the Elements
thank you
In Meteor, you can use the meteorhacks:aggregate package to implement the aggregation:
Add to your app with
meteor add meteorhacks:aggregate
Then simply use .aggregate function like below.
var coll = new Mongo.Collection('collectionName');
var pipeline = [
{ "$unwind": "$division" },
{
"$group": {
"_id": "$division.type",
"id": { "$first": "$_id" },
"categorie": { "$first": "$categorie" },
"division": {
"$push": { "nameDivision": "$division.nameDivision" }
}
}
},
{
"$group": {
"_id": "$id",
"categorie": { "$first": "$categorie" },
"division": {
"$push": {
"type": "$_id",
"division": "$division"
}
}
}
}
];
var result = coll.aggregate(pipeline);

Reduce the response of mongoDB query

I have documents like this:
{
"_id" : ObjectId("53340d07d6429d27e1284c77"),
"worktypes" : [
{
"name" : "Pompas",
"works" : [
{
"name" : "work 1",
"code" : "0001"
}
]
},
{
"name" : "Pompas "",
"works" : [
{
"name" : "work 2",
"code" : "0002"
}
]
}
]
}
I did a query for get ONLY the works of one of worktype for this document, this is the query:
db.categories.find({$and: [
{ "_id": ObjectId('53340d07d6429d27e1284c77')},
{"worktypes.name": "Pompas"}
]},{"worktypes.works.$":1})
But i got
{
"_id" : ObjectId("53340d07d6429d27e1284c77"),
"worktypes" : [
{
"name" : "Pompas",
"works" : [
{
"name" : "work 1",
"code" : "0001"
}
]
}
]
}
But i only need:
"works" : [
{
"name" : "work 1",
"code" : "0001"
}
]
How can i reduce this?
I think Neil Lunn's answer is mostly correct, but in my opinion it needs a few tweaks to get the expected result:
Match against "worktypes.name" rather than "worktypes.works.name"
In the $group phase, use $first instead of $push to get the first element alone
Add a $project phase to just get the "works"
db.categories.aggregate([
{ "$unwind": "$worktypes" },
{ "$unwind": "$worktypes.works" },
{ "$match": {
"worktypes.name": "Pompas"
}},
{ "$group": {
"_id": "$_id",
"works": { "$first": "$worktypes.works" }
}},
{ "$project": {"_id":0, "works":1} }
])
Output:
{
"result" : [
{
"works" : {
"name" : "work 1",
"code" : "0001"
}
}
],
"ok" : 1
}
You need to use the $unwind operator when working with arrays:
db.catefories.aggregate([
// Unwind the first array
{ "$unwind": "$worktypes" },
// Then unwind the embedded array
{ "$unwind": "$worktypes.works" },
// Match the item you want
{ "$match": {
"worktypes.works.name": "work 1"
}},
// Group to reform the array structure
{ "$group": {
"_id": "$_id",
"worktypes": { "$push": "$worktypes" }
}}
])
And to get back as an array use $group after the $unwind.
You can selectively remove fields from the projection like so:
db.categories.find({$and: [
{ "_id": ObjectId('53340d07d6429d27e1284c77')},
{"worktypes.name": "Pompas"}
]},{"worktypes.works.$":1, _id:0 })
this will prevent the _id field from being projected.