Retrieve field value from array of sub document - mongodb

I have some documents like this:
{
"hash": "14a076f9f6cecfc58339330eeb492e267f63062f6d5f669c7cdbfecf9eb4de32",
"started_services": [],
"deleted_files": [],
"software": {
"adobe" : {
"licenses" : [
{ "key": "2384723",
"date": "26-10-2012"
},
{ "key": "23888823",
"date": "09-11-2012"
}
]
}
}
}
How do I retrieve just the hash value and the list of "key" values?
I did the following, but, as you see, the result has the entire path which I do not want.
> db.repository.find({"$and": [{"datetime_int": {"$gte": 1451952000}},{"software.adobe.licenses.key" : { $exists : true}}]}, {hash:1, "software.adobe.licenses.key":1, _id:0}).limit(10)
{ "hash" : "a1532e0609aaf6acfa9e505e93af0bee0856a9a67398aeaa72aa6eb2fffd134e", "software" : { "adobe" : { "licenses" : [ { "key" : "2008350" }, { "key" : "2018350" }, { "key" : "2028350" }, { "key" : "2038350" }, { "key" : "2048350" }, { "key" : "2058350" }, { "key" : "2068350" }, { "key" : "2078350" }...]}}}
The result I want should look like this:
{"hash": "a1532e0609aaf6acfa9e505e93af0bee0856a9a67398aeaa72aa6eb2fffd134e",
"key": ["2008350", "2018350", "2028350", "2038350", "2048350", "2058350", "2068350", "2078350"]
}
How do I do that?

You can do this with the aggregation framework.
db.repository.aggregate([
{ "$match": {
"datetime_int": { "$gte": 1451952000 },
"software.adobe.licenses.key" : { "$exists" : true }
}},
{ "$project": {
"hash": 1,
"key": {
"$map": {
"input": "$software.adobe.licenses",
"as": "soft",
"in": "$$soft.key"
}
}
}}
])
Starting From MongoDB 3.2 you can directly project the sub-document array field.
{ "$project": { "hash": 1, "key": "$software.adobe.licenses.key"}}

db.key.aggregate((
{ "$match": {
"datetime_int": { "$gte": 1451952000 }
}},
{"$unwind":"$software.adobe.licenses"},
{"$project":{"key":"$software.adobe.licenses.key", "hash":1, "_id":0}}
))
outputs the following :
{ "hash" : "14a076f9f6cecfc58339330eeb492e267f63062f6d5f669c7cdbfecf9eb4de32", "key" : [ "2384723", "23888823" ] }

Related

How to update array of objects to LowerCase in mongodb?

I need to update the role in team array to lowercase.
db.users.find().pretty().limit(1)
{
"_id" : ObjectId("5d9fd81d3d598088d2ea5dc9"),
"employed" : "USA-Atlanta",
"firstName" : "Rory",
"siteRole" : "super admin",
"status" : "active",
"team" : [
{
"name" : "SALES AND MARKETING",
"displayName" : "S&M",
"role" : "Manager"
}
]
}
Tried this code.I m getting it with normal fields.
db.users.find( {}, { 'role': 1 } ).forEach(function(doc) {
db.users.update(
{ _id: doc._id},
{ $set : { 'role' : doc.role.toLowerCase() } },
{ multi: true }
)
});
sample output
"team" : [
{
"name" : "SALES AND MARKETING",
"displayName" : "S&M",
"role" : "manager"
}
]
I think the below Aggregation query is what you are looking for
var count = 0;
db.users.aggregate([
{
"$match": {
"team.role": {$exists: true}
}
},
{
"$project": {
"_id": 1,
// "team": 1,
"teamModified": {
"$map": {
"input": "$team",
"as": "arrayElems",
"in": {
"$mergeObjects": [
"$$arrayElems",
{"role": {"$toLower": "$$arrayElems.role"}}
]
}
}
}
}
},
]).forEach(function(it) {
db.users.updateOne({
"_id": it["_id"]
}, {
"$set": {
"team": it["teamModified"]
}
})
printjson(++count);
})
printjson("DONE!!!")
Note: I haven't tested the script properly in my local, so do let me know if it didn't help you out

Combine results based on condition during group by

Mongo query generated out of java code:
{
"pipeline": [{
"$match": {
"Id": "09cd9a5a-85c5-4948-808b-20a52d92381a"
}
},
{
"$group": {
"_id": "$result",
"id": {
"$first": "$result"
},
"labelKey": {
"$first": {
"$ifNull": ["$result",
"$result"]
}
},
"value": {
"$sum": 1
}
}
}]
}
Field 'result' can have values like Approved, Rejected, null and "" (empty string). What I am trying to achieve is combining the count of both null and empty together.
So that the empty string Id will have the count of both null and "", which is equal to 4
I'm sure theres a more "proper" way but this is what i could quickly come up with:
[
{
"$group" : {
"_id" : "$result",
"id" : {
"$first" : "$result"
},
"labelKey" : {
"$first" : {
"$ifNull" : [
"$result",
"$result"
]
}
},
"value" : {
"$sum" : 1.0
}
}
},
{
"$group" : {
"_id" : {
"$cond" : [{
$or: [
{"$eq": ["$_id", "Approved"]},
{"$eq": ["$_id", "Rejected"]},
]}},
"$_id",
""
]
},
"temp" : {
"$push" : {
"_id" : "$_id",
"labelKey" : "$labelKey"
}
},
"count" : {
"$sum" : "$value"
}
}
},
{
"$unwind" : "$temp"
},
{
"$project" : {
"_id" : "$temp._id",
"labelKey": "$temp.labelKey",
"count" : "$count"
}
}
],
);
Due to the fact the second group is only on 4 documents tops i don't feel too bad about doing this.
I have used $facet.
The MongoDB stage $facet lets you run several independent pipelines within the stage of a pipeline, all using the same data. This means that you can run several aggregations with the same preliminary stages, and successive stages.
var queries = [{
"$match": {
"Id": "09cd9a5a-85c5-4948-808b-20a52d92381a"
}
},{
$facet: {//
"empty": [
{
$match : {
result : { $in : ['',null]}
}
},{
"$group" : {
"_id" : null,
value : { $sum : 1}
}
}
],
"non_empty": [
{
$match : {
result : { $nin : ['',null]}
}
},{
"$group" : {
"_id" : '$result',
value : { $sum : 1}
}
}
]
}
},
{
$project: {
results: {
$concatArrays: [ "$empty", "$non_empty" ]
}
}
}];
Output :
{
"results": [{
"_id": null,
"value": 52 // count of both '' and null.
}, {
"_id": "Approved",
"value": 83
}, {
"_id": "Rejected",
"value": 3661
}]
}
Changing the group by like below solved the problem
{
"$group": {
"_id": {
"$ifNull": ["$result", ""]
},
"id": {
"$first": "$result"
},
"labelKey": {
"$first": {
"$ifNull": ["$result",
"$result"]
}
},
"value": {
"$sum": 1
}
}
}

MongoDB projection on specific nested properties

"data" : {
"visits" : {
"daily" : {
"2018-09-05" : 3586,
"2018-09-06" : 2969,
"2018-09-07" : 2624,
"2018-09-08" : 2803,
"2018-09-09" : 3439,
"2018-09-10" : 3655
}
}
},
I have property structure in MongoDB like this, what I am trying to do is, if i have start date and end date, for example (2018-09-06 - 2018-09-07),
I want to get result in this format
"data" : {
"visits" : {
"daily" : {
"2018-09-06" : 2969,
"2018-09-07" : 2624
}
}
},
Is there any efficient way to do it dynamically? I can do it by putting in projections things like this {"data.visits.daily.2018-09-06": 1, "data.visits.daily.2018-09-07": 1} and it works but it doesn't seem to me like a good solution.
Using MongoDB 3.4.4 and newer versions:
db.collection.aggregate([
{ "$addFields": {
"data.visits.daily": {
"$arrayToObject": {
"$filter": {
"input": { "$objectToArray": "$data.visits.daily" },
"as": "el",
"cond": {
"$and": [
{ "$gte": ["$$el.k", "2018-09-06"] },
{ "$lte": ["$$el.k", "2018-09-07"] },
]
}
}
}
}
} }
])
The above pipeline will yield the final output
{
"data" : {
"visits" : {
"daily" : {
"2018-09-06" : 2969,
"2018-09-07" : 2624
}
}
}
}
Explanations
The pipeline can be decomposed to show each individual operator's results.
$objectToArray
$objectToArray enables you to transform the document with dynamic keys
into an array that contains a element for each field/value pair in the original document. Each element in the return array is a document that contains two fields k and v.
Running the pipeline with just the operator in a $project stage
db.collection.aggregate([
{ "$project": {
"keys": { "$objectToArray": "$data.visits.daily" }
} }
])
yields
{
"_id" : ObjectId("5bab6d09b1951fef20a5dce4"),
"keys" : [
{
"k" : "2018-09-05",
"v" : 3586
},
{
"k" : "2018-09-06",
"v" : 2969
},
{
"k" : "2018-09-07",
"v" : 2624
},
{
"k" : "2018-09-08",
"v" : 2803
},
{
"k" : "2018-09-09",
"v" : 3439
},
{
"k" : "2018-09-10",
"v" : 3655
}
]
}
$filter
The $filter operator acts as a filtering mechanism for the array produced by the $objectToArray operator, works by selecting a subset of the array to return based on the specified condition which
becomes your query.
Consider the following pipeline which returns an array of the key/value pair that matches the condition "2018-09-06" <= key <= "2018-09-07"
db.collection.aggregate([
{ "$project": {
"keys": {
"$filter": {
"input": { "$objectToArray": "$data.visits.daily" },
"as": "el",
"cond": {
"$and": [
{ "$gte": ["$$el.k", "2018-09-06"] },
{ "$lte": ["$$el.k", "2018-09-07"] },
]
}
}
}
} }
])
which yields
{
"_id" : ObjectId("5bab6d09b1951fef20a5dce4"),
"keys" : [
{
"k" : "2018-09-06",
"v" : 2969
},
{
"k" : "2018-09-07",
"v" : 2624
}
]
}
$arrayToObject
This will transform the filtered array above from
[
{
"k" : "2018-09-06",
"v" : 2969
},
{
"k" : "2018-09-07",
"v" : 2624
}
]
to the original document with the dynamic key
{
"2018-09-06" : 2969,
"2018-09-07" : 2624
}
so running the pipeline
db.collection.aggregate([
{ "$project": {
"keys": {
"$arrayToObject": {
"$filter": {
"input": { "$objectToArray": "$data.visits.daily" },
"as": "el",
"cond": {
"$and": [
{ "$gte": ["$$el.k", "2018-09-06"] },
{ "$lte": ["$$el.k", "2018-09-07"] },
]
}
}
}
}
} }
])
will produce
{
"_id" : ObjectId("5bab6d09b1951fef20a5dce4"),
"keys" : {
"2018-09-06" : 2969,
"2018-09-07" : 2624
}
}
But of course you would want to preserve the original schema i.e. the current fields so you would need to use $addFields instead of the $project pipeline used for illustrated.
$addFields
This is is equivalent to a $project stage that explicitly specifies all existing fields in the input documents and adds the new fields. Specifying an existing field name in an $addFields operation causes the original field to be replaced and you would need to use dot notation to to update the embedded data.visits.daily field with the dynamic keys.
You can achieve this using the following aggregation :
var startdate = "2018-09-06";
var enddate = "2018-09-09";
db['01'].aggregate(
[
{
$project: {
daily:{$objectToArray:"$data.visits.daily"}
}
},
{
$unwind: {
path : "$daily",
}
},
{
$addFields: {
"date": {$dateFromString:{dateString:"$daily.k",format:"%Y-%m-%d"}}
}
},
{
$match: {
$and:[{date:{$gte:new Date(startdate)}},{date:{$lte:new Date(enddate)}}]
}
},
{
$group: {
_id:"_id",
daily:{$push:"$daily"}
}
},
{
$project: {
"data.visits.daily":{$arrayToObject:"$daily"}
}
},
]
);

Rewind data of two nested array field after $unwind and $lookup and $filter on date range in $project

{
"_id" : ObjectId("590b12b6330e1567acd29e69"),
"name": "Foo",
"sales_history" : [
{
"_id" : ObjectId("593ce8e4cfaa652df543d9e3"),
"sold_at" : ISODate("2017-06-11T06:53:24.881Z"),
"sold_to" : ObjectId("593509e938792e046ba14a02"),
"sold_products" : [
{
"product_dp" : 100,
"quantity" : 1,
"product_id" : ObjectId("591068be1f4c6c79a442a788"),
"_id" : ObjectId("593ce8e4cfaa652df543d9e5")
},
{
"product_dp" : 100,
"quantity" : 1,
"product_id" : ObjectId("593a33dccfaa652df543d924"),
"_id" : ObjectId("593ce8e4cfaa652df543d9e4")
}
]
},
{
"_id" : ObjectId("5944cb7142a04740357020b9"),
"sold_at" : ISODate("2017-06-17T06:25:53.332Z"),
"sold_to" : ObjectId("5927d4a59e58ba0c61066f3b"),
"sold_products" : [
{
"product_dp" : 500,
"quantity" : 1,
"price" : 5650,
"product_id" : ObjectId("593191ed53a2741dd9bffeb5"),
"_id" : ObjectId("5944cb7142a04740357020ba")
}
]
}
]
}
I have User schema like this. I want detail of product_id reference, with a date range search criteria on sold_at date field.
My expected data like following when I searched in sold_at at: 2017-06-11
{
"_id" : ObjectId("590b12b6330e1567acd29e69"),
"name": "Foo",
"sales_history" : [
{
"_id" : ObjectId("593ce8e4cfaa652df543d9e3"),
"sold_at" : ISODate("2017-06-11T06:53:24.881Z"),
"sold_to" : ObjectId("593509e938792e046ba14a02"),
"sold_products" : [
{
"product_dp" : 100,
"quantity" : 1,
"product_id": {
_id:ObjectId("hsfgg123412yh3gy1u2g3"),
name: "Product1",
code: "FG0154"
},
}
]
}
]
}
Product detail need to be populate in product_id, sales_history array need to be filtered in date range.
You can try below aggregation query.
$filter sales history on date range followed by $unwinding sales history & sold_products.
$lookup sold_products to get the product details.
$group back sold_products & sales history
db.collection.aggregate([
{
"$project": {
"name": 1,
"sales_history": {
"$filter": {
"input": "$sales_history",
"as": "history",
"cond": {
"$and": [
{
"$gte": [
"$$history.sold_at",
ISODate("2017-06-11T00:00:00.000Z")
]
},
{
"$lt": [
"$$history.sold_at",
ISODate("2017-06-12T00:00:00.000Z")
]
}
]
}
}
}
}
},
{
"$unwind": "$sales_history"
},
{
"$unwind": "$sales_history.sold_products"
},
{
"$lookup": {
"from": lookupcollection,
"localField": "sales_history.sold_products.product_id",
"foreignField": "_id",
"as": "sales_history.sold_products.product_id"
}
},
{
"$group": {
"_id": {
"_id": "$_id",
"sales_history_id": "$sales_history._id"
},
"name": {
"$first": "$name"
},
"sold_at": {
"$first": "$sales_history.sold_at"
},
"sold_to": {
"$first": "$sales_history.sold_to"
},
"sold_products": {
"$push": "$sales_history.sold_products"
}
}
},
{
"$group": {
"_id": "$_id._id",
"name": {
"$first": "$name"
},
"sales_history": {
"$push": {
"_id": "$_id.sales_history_id",
"sold_at": "$sold_at",
"sold_to": "$sold_to",
"sold_products": "$sold_products"
}
}
}
}
]);

Match key name and show document in Mongodb?

Json Structure:
"_id" : ObjectId("55d6cb28725f3019a5241781"),
"Number" : {
"value" : "1234567",
},
"DeviceID" : {
"value" : "01",
}
"type" : {
"value" : "ce06"}
Now i want to find only those keys document which start from /dev/.
i tried this script:
var cur = db.LIVEDATA.find({"ProductIMEIno.value":"359983007488004"});
cur.forEach(function(doc){
var keynames = Object.keys(doc);
print('the length is '+keynames.length);
for(var i=0;i<keynames.length;i++){
if(keynames[i].match(/Dev/)){
print("the name is "+keynames); }}} )
but this is not working properly.
Desired Output;
Only this document should show on the basis of key name search.
"DeviceID" : {
"value" : "01",
MongoDB isn't designed to find keys dynamically like this; it's much easier to use it to find values dynamically, so you could restructure your data structure to allow this:
"_id" : ObjectId("55d6cb28725f3019a5241781"),
"data" : [
{
"key" : "Number",
"value" : "1234567",
},
{
"key": "DeviceID",
"value" : "01",
},
{
"key" : "type",
"value" : "ce06"
}
]
Then you will be able to query it like this:
db.LIVEDATA.aggregate([
{$match: {"ProductIMEIno.value":"359983007488004"}},
{$unwind: "$data"},
{$match: {"data.key" : /^dev/i }}
]);
That will return data structured like this:
{
"_id" : ObjectId("55d6cb28725f3019a5241781"),
"data" : {
"key" : "DeviceID",
"value" : "01"
}
}
Suppose you have a data collection like this:
[
{
"Number": {
"value": "1234567"
},
"DeviceID": {
"value": "01"
},
"DeviceID2": {
"value": "01",
"name": "abc123"
},
"type": {
"value": "ce06"
}
},
{
"Number": {
"value": "1234568"
},
"DeviceID": {
"value": "02"
},
"type": {
"value": "ce07"
}
}
]
You can use following aggregation:
db.collection.aggregate([
{
"$match": {}
},
{
"$addFields": {
"root_key_value_list": {
"$objectToArray": "$$ROOT"
}
}
},
{
"$unwind": "$root_key_value_list"
},
{
"$match": {
"root_key_value_list.k": {
"$regex": "^Dev"
}
}
},
{
"$group": {
"_id": "$_id",
"root_key_value_list": {
"$push": "$root_key_value_list"
}
}
},
{
"$project": {
"root": {
"$arrayToObject": "$root_key_value_list"
}
}
},
{
"$replaceRoot": {
"newRoot": "$root"
}
}
])
the result will be:
[
{
"DeviceID": {
"value": "01"
},
"DeviceID2": {
"name": "abc123",
"value": "01"
}
},
{
"DeviceID": {
"value": "02"
}
}
]
playground:
https://mongoplayground.net/p/z5EeHALCqzy