Getting data from subdocument between dates - mongodb

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.

Related

match element in the array with aggregation

i have mongo db collection the follwing structure
{
{
"_id" : ObjectId("63e37afe7a3453d5014c011b"),
"schemaVersion" : NumberInt(1),
"Id" : "ObjectId("63e37afe7a3453d5014c0112")",
"Id1" : "ObjectId("63e37afe7a3453d5014c0113")",
"Id2" : "ObjectId("63e37afe7a3453d5014c0114")",
"collectionName" : "Country",
"List" : [
{
"countryId" : NumberInt(1),
"name" : "Afghanistan",
},{
"countryId" : NumberInt(1),
"name" : "India",
},
{
"countryId" : NumberInt(1),
"name" : "USA",
}
}
i need to match the value with id, id1, id2, collectionName and name in the list to get country id for example if match the below value
"Id" : "ObjectId("63e37afe7a3453d5014c0112")",
"Id1" : "ObjectId("63e37afe7a3453d5014c0113")",
"Id2" : "ObjectId("63e37afe7a3453d5014c0114")",
"collectionName" : "Country",
"name" : "Afghanistan",
i need result
{
"countryId" : 1,
"name" : "Afghanistan",
}
i tried like below
db.country_admin.aggregate([
{ $match: { collectionName: "Country" } },
{ $unwind : '$countryList' },
{ $project : { _id : 0, 'countryList.name' : 1, 'countryList.countryId' : 1 } }
]).pretty()
and i have following output
[
{
"List" : {
"countryId" : 1.0,
"name" : "Afghanistan"
}
},
{
"List" : {
"countryId" : 2.0,
"name" : "india"
}
},
{
"List" : {
"countryId" : 3.0,
"name" : "USA"
}
}]```
You can try using $filter to avoid $unwind like this example:
First $match by your desired condition(s).
Then $filter and get the first element (as "List.name": "Afghanistan" is used into $match stage there will be at least one result).
And output only values you want using $project.
db.collection.aggregate([
{
"$match": {
"Id": ObjectId("63e37afe7a3453d5014c0112"),
"Id1": ObjectId("63e37afe7a3453d5014c0113"),
"Id2": ObjectId("63e37afe7a3453d5014c0114"),
"collectionName": "Country",
"List.name": "Afghanistan",
}
},
{
"$project": {
"country": {
"$arrayElemAt": [
{
"$filter": {
"input": "$List",
"cond": {
"$eq": [
"$$this.name",
"Afghanistan"
]
}
}
},
0
]
}
}
},
{
"$project": {
"_id": 0,
"countryId": "$country.countryId",
"name": "$country.name"
}
}
])
Example here
By the way, using $unwind is also possible and you can check this example

Mongodb - Array indexed by string in $addToSet operator

Suppose we have these two documents:
{
"_id" : ObjectId("5f3cdd1d0fefeba343ff3093"),
"country" : "C1",
"time" : "1994",
"value" : NumberInt(100),
"type" : "type1",
"origin" : "O1"
}
{
"_id" : ObjectId("5f3cdd1d0fefeba343ff3094"),
"country" : "C1",
"time" : "1994",
"value" : NumberInt(200),
"type" : "type1",
"origin" : "O2"
}
I want to retrieve the aggregation with the origin array indexed by strings (the value of "type"); expected output:
{
"_id" : {
"country" : "C1",
"time" : "1994"
},
"TOT" : NumberInt(300),
"count" : 2.0,
"origin" : [
"O1": NumberInt(100),
"O2": NumberInt(200)
]
}
Here we have the type of the "origin" array as {[key: string]: number}.
With the following query, the origin array is instead indexed by numbers:
use local;
db.getCollection("test_collection").aggregate(
[
{
"$match" : {
"type" : {
"$in" : [
"type1"
]
}
}
},
{
"$group" : {
"_id" : {
"country" : "$country",
"time" : "$time"
},
"TOT" : {
"$sum" : "$value"
},
"count" : {
"$sum" : 1.0
},
"origin" : {
"$addToSet" : "$value"
}
}
}
],
{
"allowDiskUse" : false
}
);
You can try $arrayToObject after adding in origin,
use local;
db.getCollection("test_collection").aggregate([
{ "$match": { "type": { "$in": ["type1"] } } },
{
"$group": {
"_id": {
"country": "$country",
"time": "$time"
},
"TOT": { "$sum": "$value" },
"count": { "$sum": 1.0 },
// add object like this
"origin": {
"$addToSet": {
k: "$origin",
v: "$value"
}
}
}
},
// add this
{ $addFields: { origin: { $arrayToObject: "$origin" } } }
],
{ "allowDiskUse": false }
)
Playground

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

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 .

mongodb aggregation $group and then $push a object

this is my data :
> db.bookmarks.find({"userId" : "56b9b74bf976ab70ff6b9999"}).pretty()
{
"_id" : ObjectId("56c2210fee4a33579f4202dd"),
"userId" : "56b9b74bf976ab70ff6b9999",
"items" : [
{
"itemId" : "28",
"timestamp" : "2016-02-12T18:07:28Z"
},
{
"itemId" : "29",
"timestamp" : "2016-02-12T18:07:29Z"
},
{
"itemId" : "30",
"timestamp" : "2016-02-12T18:07:30Z"
},
{
"itemId" : "31",
"timestamp" : "2016-02-12T18:07:31Z"
},
{
"itemId" : "32",
"timestamp" : "2016-02-12T18:07:32Z"
},
{
"itemId" : "33",
"timestamp" : "2016-02-12T18:07:33Z"
},
{
"itemId" : "34",
"timestamp" : "2016-02-12T18:07:34Z"
}
]
}
I want to have something like (actually i hope the _id can become userId too) :
{
"_id" : "56b9b74bf976ab70ff6b9999",
"items" : [
{ "itemId": "32", "timestamp": "2016-02-12T18:07:32Z" },
{ "itemId": "31", "timestamp": "2016-02-12T18:07:31Z" },
{ "itemId": "30", "timestamp": "2016-02-12T18:07:30Z" }
]
}
What I have now :
> db.bookmarks.aggregate(
... { $match: { "userId" : "56b9b74bf976ab70ff6b9999" } },
... { $unwind: '$items' },
... { $sort: { 'items.timestamp': -1} },
... { $skip: 2 },
... { $limit: 3},
... { $group: { '_id': '$userId' , items: { $push: '$items.itemId' } } }
... ).pretty()
{ "_id" : "56b9b74bf976ab70ff6b9999", "items" : [ "32", "31", "30" ] }
i tried to read the document in mongo and find out i can $push, but somehow i cannot find a way to push such object, which is not defined anywhere in the whole object. I want to have the timestamp also.. but i don't know how should i modified the $group (or others??) to do so. thanks for helping!
This code, which I tested in the MongoDB 3.2.1 shell, should give you the output format that you want:
> db.bookmarks.aggregate(
{ "$match" : { "userId" : "Ursula" } },
{ "$unwind" : "$items" },
{ "$sort" : { "items.timestamp" : -1 } },
{ "$skip" : 2 },
{ "$limit" : 3 },
{ "$group" : { "_id" : "$userId", items: { "$push" : { "myPlace" : "$items.itemId", "myStamp" : "$items.timestamp" } } } } ).pretty()
Running the above will produce this output:
{
"_id" : "Ursula",
"items" : [
{
"myPlace" : "52",
"myStamp" : ISODate("2016-02-13T18:07:32Z")
},
{
"myPlace" : "51",
"myStamp" : ISODate("2016-02-13T18:07:31Z")
},
{
"myPlace" : "50",
"myStamp" : ISODate("2016-02-13T18:07:30Z")
}
]
}
In MongoDB version 3.2.x, you can also use the $out operator in the very last stage of the aggregation pipeline, and have the output of the aggregation query written to a collection. Here is the code I used:
> db.bookmarks.aggregate(
{ "$match" : { "userId" : "Ursula" } },
{ "$unwind" : "$items" },
{ "$sort" : { "items.timestamp" : -1 } },
{ "$skip" : 2 },
{ "$limit" : 3 },
{ "$group" : { "_id" : "$userId", items: { "$push" : { "myPlace" : "$items.itemId", "myStamp" : "$items.timestamp" } } } },
{ "$out" : "ursula" } )
This gives me a collection named "ursula":
> show collections
ursula
and I can query that collection:
> db.ursula.find().pretty()
{
"_id" : "Ursula",
"items" : [
{
"myPlace" : "52",
"myStamp" : ISODate("2016-02-13T18:07:32Z")
},
{
"myPlace" : "51",
"myStamp" : ISODate("2016-02-13T18:07:31Z")
},
{
"myPlace" : "50",
"myStamp" : ISODate("2016-02-13T18:07:30Z")
}
]
}
>
Last of all, this is the input document I used in the aggregation query. You can compare this document to how I coded the aggregation query to see how I built the new items array.
> db.bookmarks.find( { "userId" : "Ursula" } ).pretty()
{
"_id" : ObjectId("56c240ed55f2f6004dc3b25c"),
"userId" : "Ursula",
"items" : [
{
"itemId" : "48",
"timestamp" : ISODate("2016-02-13T18:07:28Z")
},
{
"itemId" : "49",
"timestamp" : ISODate("2016-02-13T18:07:29Z")
},
{
"itemId" : "50",
"timestamp" : ISODate("2016-02-13T18:07:30Z")
},
{
"itemId" : "51",
"timestamp" : ISODate("2016-02-13T18:07:31Z")
},
{
"itemId" : "52",
"timestamp" : ISODate("2016-02-13T18:07:32Z")
},
{
"itemId" : "53",
"timestamp" : ISODate("2016-02-13T18:07:33Z")
},
{
"itemId" : "54",
"timestamp" : ISODate("2016-02-13T18:07:34Z")
}
]
}

mongodb aggregation match multiple $and on the same field

i have a document like this :
{
"ExtraFields" : [
{
"value" : "print",
"fieldID" : ObjectId("5535627631efa0843554b0ea")
},
{
"value" : "14",
"fieldID" : ObjectId("5535627631efa0843554b0eb")
},
{
"value" : "POLYE",
"fieldID" : ObjectId("5535627631efa0843554b0ec")
},
{
"value" : "30",
"fieldID" : ObjectId("5535627631efa0843554b0ed")
},
{
"value" : "0",
"fieldID" : ObjectId("5535627631efa0843554b0ee")
},
{
"value" : "0",
"fieldID" : ObjectId("5535627731efa0843554b0ef")
},
{
"value" : "0",
"fieldID" : ObjectId("5535627831efa0843554b0f0")
},
{
"value" : "42",
"fieldID" : ObjectId("5535627831efa0843554b0f1")
},
{
"value" : "30",
"fieldID" : ObjectId("5535627831efa0843554b0f2")
},
{
"value" : "14",
"fieldID" : ObjectId("5535627831efa0843554b0f3")
},
{
"value" : "19",
"fieldID" : ObjectId("5535627831efa0843554b0f4")
}
],
"id" : ObjectId("55369e60733e4914550832d0"), "title" : "A product"
}
what i want is to match one or more sets from the ExtraFields array. For example, all the products that contain the values print and 30. Since a value may be found in more than one fieldID (like 0 or true) we need to create a set like
WHERE (fieldID : ObjectId("5535627631efa0843554b0ea"), value : "print")
Where i'm having problems is when querying more than one fields. The pipeline i came up with is :
db.products.aggregate([
{'$unwind': '$ExtraFields'},
{
'$match': {
'$and': [{
'$and': [{'ExtraFields.value': {'$in': ["A52A2A"]}}, {
'ExtraFields.fieldID': ObjectId("5535627631efa0843554b0ea")
}]
}
,
{
'$and': [{'ExtraFields.value': '14'}, {'ExtraFields.fieldID': ObjectId("5535627631efa0843554b0eb")}]
}
]
}
},
]);
This returns zero results, but this is what i want to do in theory. Match all items that contain set 1 AND all that contain set 2.
The end result should look like a faceted search output :
[
{
"_id" : {
"values" : "18",
"fieldID" : ObjectId("5535627831efa0843554b0f3")
},
"count" : 2
},
{
"_id" : {
"values" : "33",
"fieldID" : ObjectId("5535627831efa0843554b0f2")
},
"count" : 1
}
]
Any ideas?
You could try the following aggregation pipeline
db.products.aggregate([
{
"$match": {
"ExtraFields.value": { "$in": ["A52A2A", "14"] },
"ExtraFields.fieldID": {
"$in": [
ObjectId("5535627631efa0843554b0ea"),
ObjectId("5535627631efa0843554b0eb")
]
}
}
},
{
"$unwind": "$ExtraFields"
},
{
"$match": {
"ExtraFields.value": { "$in": ["A52A2A", "14"] },
"ExtraFields.fieldID": {
"$in": [
ObjectId("5535627631efa0843554b0ea"),
ObjectId("5535627631efa0843554b0eb")
]
}
}
},
{
"$group": {
"_id": {
"value": "$ExtraFields.value",
"fieldID": "$ExtraFields.fieldID"
},
"count": {
"$sum": 1
}
}
}
])
With the sample document provided, this gives the output:
/* 1 */
{
"result" : [
{
"_id" : {
"value" : "14",
"fieldID" : ObjectId("5535627631efa0843554b0eb")
},
"count" : 1
}
],
"ok" : 1
}