MongoDB Grouping and Project Custom Fields - mongodb

I am stuck with MongoDB grouping and project custom fields. I have the following collection:
{
"DescId" : "1",
"Desc" : "Testing",
"ParentId" : "null",
"Order" : 1.0,
"Type" : "A",
"Parent" : null
}
{
"DescId" : "1.1",
"Desc" : "Testing Child 1",
"ParentId" : "1",
"Order" : 1.0,
"Type" : "B",
"Parent" : "Testing"
}
{
"DescId" : "1.2",
"Desc" : "Testing Child 2",
"ParentId" : "1",
"Order" : 2.0,
"Type" : "B",
"Parent" : "Testing"
}
I have done following grouping based on Type, DescId, Desc fields and projected DescId and Desc
db.getCollection("GenericData").aggregate(
[
{
"$group" : {
"_id" : {
"Type" : "$Type",
"DescId" : "$DescId",
"DescName" : "$Desc"
}
}
},
{
"$project" : {
"_id" : 0.0,
"Id" : "$_id.DescId",
"Name" : "$_id.DescName",
"Type" : "$_id.Type"
}
}
],
{
"allowDiskUse" : false
}
);
This is the output I am getting:
{
"Id" : "1.2",
"Name" : "Testing Child 2",
"Type" : "B"
}
{
"Id" : "1.1",
"Name" : "Testing Child 1",
"Type" : "B"
}
{
"Id" : "1",
"Name" : "Testing",
"Type" : "A"
}
Would it be possible to project fields based on Type field, like concatenating Type value with field name like following:
{
"A" + "Id" : "1",
"A" + "Name" : "Testing"
},
{
"B" + "Id" : "1.1",
"B" + "Name" : "Testing Child 1"
}
{
"B" + "Id" : "1.2",
"B" + "Name" : "Testing Child 2"
}

To rename your object's keys you have to use $objectToArray and $arrayToObject operators. First one can convert your $$ROOT object into an array of keys and values. Then you can apply $map to modify keys (using $concat) and $filter to exclude Type key. Then you can convert that array back to an object using $arrayToObject and promote that object to a root level using $replaceRoot. So you can add below stage to your aggregation pipeline:
db.GenericData.aggregate([
{
$replaceRoot: {
newRoot: {
$arrayToObject: {
$map: {
input: {
$filter: {
input: { $objectToArray: "$$ROOT" },
as: "kv",
cond: { $in: [ "$$kv.k", [ "Id", "Name" ] ] }
}
},
as: "kv",
in: {
k: { $concat: [ "$Type", "$$kv.k" ] },
v: "$$kv.v"
}
}
}
}
}
}
])
outputs:
{ "BId" : "1.2", "BName" : "Testing Child 2" }
{ "BId" : "1.1", "BName" : "Testing Child 1" }
{ "AId" : "1", "AName" : "Testing" }
EDIT:
If you'd like to explicitly specify which properties should be projected you can use $in operator inside $filter

Related

All Mongo documents with duplicated objects inside array

Find documents with duplicated objects inside an array.
Some answers works just with array made of "basic type elements" (i.e. array of strings). Here I want to filter on certain objects fields
In example:
{
"name": "1",
"arr": [{ "type": "fruit", "name":"pear"},{ "type": "fruit","name":"banana"}]
},
{
"name":"2",
"arr": [{"type":"fish"}]
}
Given the above two documents, I want to retrieve just document 1, because it has 2 elements in the array that have the same type. (Of course I want all documents with such property, not just one)
The following query can get us the expected output:
db.collection.find({
$expr:{
$ne:[
{
$size:"$arr"
},
{
$size:{
$setUnion:["$arr.type"]
}
}
]
}
}).pretty()
Data set:
{
"_id" : ObjectId("5d7b8546d76ccfa3cb0f133c"),
"name" : "1",
"arr" : [
{
"type" : "fruit",
"name" : "pear"
},
{
"type" : "fruit",
"name" : "banana"
}
]
}
{
"_id" : ObjectId("5d7b8546d76ccfa3cb0f133d"),
"name" : "2",
"arr" : [
{
"type" : "fish"
}
]
}
{
"_id" : ObjectId("5d7b8546d76ccfa3cb0f133e"),
"name" : "3",
"arr" : [
{
"type" : "product",
"name" : "watch"
},
{
"type" : "product",
"name" : "Pen"
}
]
}
Output:
{
"_id" : ObjectId("5d7b8546d76ccfa3cb0f133c"),
"name" : "1",
"arr" : [
{
"type" : "fruit",
"name" : "pear"
},
{
"type" : "fruit",
"name" : "banana"
}
]
}
{
"_id" : ObjectId("5d7b8546d76ccfa3cb0f133e"),
"name" : "3",
"arr" : [
{
"type" : "product",
"name" : "watch"
},
{
"type" : "product",
"name" : "Pen"
}
]
}
Query analysis: We are filtering documents in which the size of arr is not equal to the count of unique type present in the arr

Update double nested array mongodb

I have the below document which contains double nested array format. I have to update the "level" field to "Senior Engineer" when the "someKey":"somevalue" and "Company":"Company1" and "Name":"Nandhi".
Document
{
"_id" : "777",
"someKey" : "someValue",
"someArray" : [
{
"Company" : "Company1",
"someNestedArray" : [
{
"name" : "Nandhi",
"level" : "Junior Engineer"
},
{
"name" : "Rajan",
"level" : "Senio Engineer"
}
]
}],
{
"Company" : "Company2",
"someNestedArray" : [
{
"name" : "Nandhi",
"level" : "Junior Engineer"
},
{
"name" : "Rajan",
"level" : "Senio Engineer"
}
]
}
]
}
Update Query I tried
db.Test123.updateOne(
{"someKey" : "someValue","someArray.Company":"Company1"},
{$set:{"someArray.$[someNestedArray].level":"Senior Developer"}},
{arrayFilters:[{"someNestedArray.name":"Nandhi"}]}
);
Output Screenshot
As you can seen that, the modifiedCount returns 0. Please advice on this!
You need to define arrayFilter for every level of nesting, try:
db.Test123.update(
{ "someKey" : "someValue" },
{ "$set": { "someArray.$[someArrayDoc].someNestedArray.$[someNestedArrayDoc].level": "Senior Developer" } },
{ arrayFilters: [ {"someArrayDoc.Company": "Company1"}, { "someNestedArrayDoc.name": "Nandhi" } ] }
)

How to create view to read from two collections in mongoDB?

Started with mongoDB syntax and use in project.
I am looking for a solution where I can combine more than two collections with couple of condition to create a view.
Here is my collection Range
/* 1 */
{
"_id" : ObjectId("1"),
"range" : {
"start" : "00"
},
"products" : [
{
"id" : "01",
"name" : "FirstProduct",
"type" : "First Type"
},
{
"id" : "02",
"name" : "Second Product",
"type" : "Second Type"
},
{
"id" : "03",
"name" : "Third Product",
"type" : "Third Type"
},
]
}
/* 2 */
{
"_id" : ObjectId("2"),
"range" : {
"start" : "100",
},
"products" : [
{
"id" : "01",
"name" : "First Product",
"type" : "First Type"
},
{
"id" : "02",
"name" : "Second Product",
"type" : "Second Type"
}
]
}
/* 3 */
{
"_id" : ObjectId("3"),
"range" : {
"start" : "500",
},
"products" : [
{
"id" : "01",
"name" : "First Product",
"type" : "First Type"
},
{
"id" : "02",
"name" : "Second Product",
"type" : "Second Type"
}
]
}
Second Collection. Stock
/* 1 */
{
"_id" : ObjectId("1"),
"range" : {
"start" : "00"
},
"products" : [
{
"id" : "01",
"expired" : false,
"returned" : false
},
{
"id" : "02",
"expired" : false,
"returned" : false
}
]
}
/* 2 */
{
"_id" : ObjectId("02"),
"range" : {
"start" : "100"
},
"products" : [
{
"id" : "01",
"expired" : true,
"returned" : true
},
{
"id" : "02",
"expired" : true,
"returned" : true
}
{
"id" : "03",
"expired" : true,
"returned" : true
}
]
}
Now want to have a view with combine result from above two collection above.
For each range document in Range collections
if Range.range.start = Stock.range.start
if Range.products.id = Stock.products.id
copy "expired" and "returned" field from Stock for that product and
add to Range.product
end if
end if
Return Range
So final result will something like below.
/* 1 */
{
"_id" : ObjectId("1"),
"range" : {
"start" : "00"
},
"products" : [
{
"id" : "01",
"name" : "FirstProduct",
"type" : "First Type"
"expired" : false,
"returned" : false
},
{
"id" : "02",
"name" : "Second Product",
"type" : "Second Type"
"expired" : false,
"returned" : false
}
]
}
/* 2 */
{
"_id" : ObjectId("2"),
"range" : {
"start" : "100",
},
"products" : [
{
"id" : "01",
"name" : "First Product",
"type" : "First Type",
"expired" : true,
"returned" : true
},
{
"id" : "02",
"name" : "Second Product",
"type" : "Second Type",
"expired" : true,
"returned" : true
}
]
}
/* 3 */
{
"_id" : ObjectId("3"),
"range" : {
"start" : "500",
},
"products" : [
{
"id" : "01",
"name" : "First Product",
"type" : "First Type"
},
{
"id" : "02",
"name" : "Second Product",
"type" : "Second Type"
}
]
}
I started with aggregate pipeline stages with fail to get right queries.
if anyone can help with right syntax and proper aggregate function.
Thanks in advance.
You need $lookup to merge the data from both collections but then you have to use $unwind to be able to match corresponding documents by product.id. In the last step you can use $group to get back an array:
db.Range.aggregate([
{
$lookup: {
from: "Stock",
localField: "range.start",
foreignField: "range.start",
as: "stock"
}
},
{
$unwind: "$stock"
},
{
$unwind: "$products"
},
{
$unwind: "$stock.products"
},
{
$match: { $expr: { $eq: [ "$products.id", "$stock.products.id" ] } }
},
{
$group: {
_id: "$_id",
"range": { $first: "$range" },
products: {
$push: {
id: "$products.id",
name: "$products.name",
type: "$products.type",
expired: "$stock.products.expired",
returned: "$stock.products.returned"
}
}
}
}
])
EDIT: Alternative solution which operates directly on arrays using $map and $filter below. The drawback is that the code is less readable but the good part is that it should return documents when there's no match and you should get better performance using this approach
db.Range.aggregate([
{
$lookup: {
from: "Stock",
localField: "range.start",
foreignField: "range.start",
as: "stock"
}
},
{
$unwind: "$stock"
},
{
$addFields: {
products: {
$map: {
input: "$products",
as: "p",
in: {
$let: {
vars: {
stockItem: {
$arrayElemAt: [
{ $filter: { input: "$stock.products", cond: { $eq: [ "$$p.id", "$$this.id" ] } } }, 0
]
}
},
in: {
$cond: [
{ $eq: [ "$$stockItem", undefined ] },
"$$p",
{
id: "$$p.id",
name: "$$p.name",
type: "$$p.type",
expired: "$$stockItem.expired",
returned: "$$stockItem.returned",
}
]
}
}
}
}
}
}
},
{
$project: {
stock: 0
}
}
])

Project only some fields of array items in sub document

How can I project only particular fields of items in array in sub document?
Consider the following (simplified) example:
{
"_id" : ObjectId("573d70df080cc2cbe8bf3222"),
"name" : "Nissan",
"models" : [
{
"name" : "Altima",
"body" : {
"type" : 2,
"maxprice" : 31800.00,
"minprice" : 21500.00
}
},
{
"name" : "Maxima",
"body" : {
"type" : 2,
"maxprice" : 39200.00,
"minprice" : 28800.00
}
}
]
},
{
"_id" : ObjectId("80cc2cbe8bf3222573d70df0"),
"name" : "Honda",
"models" : [
{
"name" : "Accord",
"body" : {
"type" : 2,
"maxprice" : 34100.00,
"minprice" : 20400.00
}
},
{
"name" : "Civic",
"body" : {
"type" : 3,
"maxprice" : 27900.00,
"minprice" : 19800.00
}
}
]
}
After aggregation, I'd like to get the following output:
{
"_id" : ObjectId("573d70df080cc2cbe8bf3222"),
"name" : "Nissan",
"models" : [
{
"type" : 2,
"minprice" : 21500.00
},
{
"type" : 2,
"minprice" : 28800.00
}
]
},
{
"_id" : ObjectId("80cc2cbe8bf3222573d70df0"),
"name" : "Honda",
"models" : [
{
"type" : 2,
"minprice" : 20400.00
},
{
"type" : 3,
"minprice" : 19800.00
}
]
}
So it basically gets all documents, all fields of documents, all items in models array, BUT only some fields of the array items in models. Please help.
You need to $project the "models" field using the $map operator.
db.collection.aggregate([
{ "$project": {
"name": 1,
"models": {
"$map": {
"input": "$models",
"as": "m",
"in": {
"type": "$$m.body.type",
"minprice": "$$m.body.minprice"
}
}
}
}}
])
$unwind is your friend
First you can basically filter the (non nested) fields you want.
var projection = {$project:{name:'$name', models:'$models'}};
db.dum.aggregate(projection)
Foreach of your models, you issue a document
var unwindModels = {$unwind:{'$models'}}
db.dum.aggregate(projection, unwindModels)
The idea is that every document issued from your models field will be regrouped later on via the _id field.
Foreach document, you only keep the (sub)fields you want
var keepSubFields = {$project:{name:'$name', type:'$models.body.type', minprice:'$models.body.minprice'}}
db.dum.aggregate(projection, unwindModels, keepSubFields)
Then you reaggregate your models as an array (thanks to the _id of each record which tracks the original record)
var aggregateModels = {$group:{_id:'$_id', name:{$last:'$name'}, models:{$push:{type:'$type', minprice:'$minprice'}}}}
db.dum.aggregate(projection, unwindModels, keepSubFields, aggregateModels)
note1: Here we can use $last because our primary key is not _id but <_id, name>. ($first would be good too)
note2: we refer type by $type, because when you iterate the collection on the aggregateModels stage, your record is of the form
<_id, name, type, minprice>

In MongoDb, how to apply sort internal fields present in document?

My document looks like this
{
field1: somevalue,
name:xtz
nested_documents: [ // array of nested document
{ x:"1", y:"2" }, // first nested document
{ x:"2", y:"3" }, // second nested document
{ x:"-1", y:"3" }, // second nested document
// ...many more nested documents
]
}
How one can sort the data present in nested_documents?
Expected answer is shown below:
nested_documents: [ { x:"-1", y:"3" },{ x:"1", y:"2" },{ x:"2", y:"3" }]
To do this you would have to use the aggregation framework
db.test.aggregate([{$unwind:'$nested_documents'},{$sort:{'nested_documents.x':
1}}])
this returns
"result" : [
{
"_id" : ObjectId("5139ba3dcd4e11c83f4cea12"),
"field1" : "somevalue",
"name" : "xtz",
"nested_documents" : {
"x" : "-1",
"y" : "3"
}
},
{
"_id" : ObjectId("5139ba3dcd4e11c83f4cea12"),
"field1" : "somevalue",
"name" : "xtz",
"nested_documents" : {
"x" : "1",
"y" : "2"
}
},
{
"_id" : ObjectId("5139ba3dcd4e11c83f4cea12"),
"field1" : "somevalue",
"name" : "xtz",
"nested_documents" : {
"x" : "2",
"y" : "3"
}
}
],
"ok" : 1
Hope this helps