Dynamic fields array in to single object mongo db - mongodb

I am having mongo collection like below,
{
"_id" : ObjectId("62aeb8301ed12a14a8873df1"),
"Fields" : [
{
"FieldId" : "e8efd0b0-9d10-4584-bb11-5b24f189c03b",
"Value" : [
"test_123"
]
},
{
"FieldId" : "fa6745c2-b259-4a3b-8c6f-19eb78fbbbf5",
"Value" : [
"123"
]
},
{
"FieldId" : "2a1be5d0-8fb6-4b06-a253-55337bfe4bcd",
"Value" : []
},
{
"FieldId" : "eed12747-0923-4290-b09c-5a05107f5609",
"Value" : [
"234234234"
]
},
{
"FieldId" : "fe41d8fb-fa18-4fe5-b047-854403aa4d84",
"Value" : [
"Irrelevan"
]
},
{
"FieldId" : "93e46476-bf2e-44eb-ac73-134403220e9e",
"Value" : [
"test"
]
},
{
"FieldId" : "db434aca-8df3-4caf-bdd7-3ec23252c2c8",
"Value" : [
"2019-06-16T18:30:00.000Z"
]
},
{
"FieldId" : "00df903f-5d59-41c1-a3df-60eeafb77d10",
"Value" : [
"tewt"
]
},
{
"FieldId" : "e97d0386-cd42-6277-1207-e674c3268cec",
"Value" : [
"1"
]
},
{
"FieldId" : "35e55d27-7d2c-467d-8a88-09ad6c9f5631",
"Value" : [
"10"
]
}
]
}
This is all dynamic form fields.
So I want to query and get result like to below object,
{
"_id" : ObjectId("62aeb8301ed12a14a8873df1"),
"e8efd0b0-9d10-4584-bb11-5b24f189c03b": ["test_123"],
"fa6745c2-b259-4a3b-8c6f-19eb78fbbbf5": ["123"],
"2a1be5d0-8fb6-4b06-a253-55337bfe4bcd": [],
"eed12747-0923-4290-b09c-5a05107f5609": ["234234234"],
"fe41d8fb-fa18-4fe5-b047-854403aa4d84": ["Irrelevan"],
"93e46476-bf2e-44eb-ac73-134403220e9e":["test"],
"db434aca-8df3-4caf-bdd7-3ec23252c2c8":["2019-06-16T18:30:00.000Z"],
"00df903f-5d59-41c1-a3df-60eeafb77d10":["1"]
}
I want final output like this combination of fields Fields.FieldID should be key and Fields.Value should be value here.
Please try to help to me to form the object like above.
Thanks in advance!

You can restructure your objects using $arrayToObject, then using that value to as a new root $replaceRoot like so:
db.collection.aggregate([
{
$match: {
// your query here
}
},
{
$project: {
newRoot: {
"$arrayToObject": {
$map: {
input: "$Fields",
in: {
k: "$$this.FieldId",
v: "$$this.Value"
}
}
}
}
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [
"$newRoot",
{
_id: "$_id"
}
]
}
}
}
])
Mongo Playground

I try this and get result like you want
db.collection.aggregate([{
$replaceWith: {
$mergeObjects: [
{
_id: "$_id"
},
{
$arrayToObject: { $zip: {inputs: ["$Fields.FieldId","$Fields.Value"]}}
}
]
}
}])
playground

Related

Query nested array from document

Given the following document data in collection called 'blah'...
[
{
"_id" : ObjectId("60913f55987438922d5f0db6"),
"procedureCode" : "code1",
"description" : "Description 1",
"coding" : [
{
"system" : "ABC",
"code" : "L111"
},
{
"system" : "DEFG",
"code" : "S222"
}
]
},
{
"_id" : ObjectId("60913f55987438922d5f0dbc"),
"procedureCode" : "code2",
"description" : "Description 2",
"coding" : [
{
"system" : "ABC",
"code" : "L999"
},
{
"system" : "DEFG",
"code" : "X3333"
}
]
}
]
What I want to get is all of the coding elements where system is ABC for all parents, and an array of codes like so.
[
{ "code": "L111" },
{ "code": "L999" },
]
If I use db.getCollection('blah').find({"coding.system": "ABC"}) I get the parent document with any child in the coding array of ICD.
If I use...
db.getCollection("blah")
.find({ "coding.system": "ABC" })
.projection({ "coding.code": 1 })
I do get the parent documents which have a child with a system of "ABC", but the coding for "DEFG" seems to come along for the ride too.
{
"_id" : ObjectId("60913f55987438922d5f0db6"),
"coding" : [
{
"code" : "L989"
},
{
"code" : "S102"
}
]
},
{
"_id" : ObjectId("60913f55987438922d5f0dbc"),
"coding" : [
{
"code" : "L989"
},
{
"code" : "X382"
}
]
}
I have also tried experimenting with:
db.getCollection("blah").aggregate(
{ $unwind: "$coding" },
{ $match: { "system": "ICD" } }
);
.. as per this page: mongoDB query to find the document in nested array
... but go no where fast with that approach. i.e. no records at all.
What query do I need, please, to achieve something like this..?
[
{ "code": "L111" },
{ "code": "L999" },
...
]
or even better, this..?
[
"L111",
"L999",
...
]
db.collection.aggregate([
{
$match: { "coding.system": "ABC" }
},
{
$unwind: "$coding"
},
{
$match: { "coding.system": "ABC" }
},
{
$project: { code: "$coding.code" }
}
])
mongoplayground
db.collection.aggregate([
{
$match: { "coding.system": "ABC" }
},
{
$unwind: "$coding"
},
{
$match: { "coding.system": "ABC" }
},
{
$group: {
_id: null,
coding: { $push: "$coding.code" }
}
}
])
mongoplayground
Instead of $unwind, $match you can also use $filter:
db.collection.aggregate([
{ $match: { "coding.system": "ABC" } },
{
$project: {
coding: {
$filter: {
input: "$coding",
cond: { $eq: [ "$$this.system", "ABC" ] }
}
}
}
}
])

$divide elements of Embedded Documents - MongoDB Aggregation

I am trying to create an aggregation MongoDB query.
Structure of data:
{
"object_name": Example,
"values": [ {"name":"value1", "value":1},
{"name":"value2", "value":10},
{"name":"total", "value":105}
}
Goal: Find object names where value1/total > 0.5 and value2/total > 0.25 and total > 100.
The data is structured in this way to provide indexes on the value_name and value fields.
What I tried - aggregate with the following pipelines:
$match: filter documents with total > 100:
$match: { values: { $elemMatch: { value_name: "total", value: {$gte: 100 }
$project: grab only the value_names that we need (there are close to 200 different names)
$project: {
values: {
$filter: {
input: "$values",
as: "value",
cond: { $or: [
{ $eq: [ "$$value.name", "name1"] },
{ $eq: [ "$$value.name", "name2"] },
{ $eq: [ "$$value.name", "total"] },
] }
}
},
name: 1
}
then, { $unwind: "$values" }
And here, I could $group to $divide: name1/total, name2/total however I'm stuck on how to get those values.
I can't simply do stats.value: because it does not know which value I'm referring to. I believe $group can't do $elemMatch to also match the name.
If there are simpler solutions that this, I'd greatly appreciate your input.
Please try this :
We're filtering documents where values array has an object with
name : total & value > 100.
Adding object with name : total
to document.
Leaving only objects that match with criteria
value1/total > 0.5 and value2/total > 0.25 in values array.
If
size of that array is greater than 1, then those two conditions are
met.
Finally projecting only object_name
Query :
db.yourCollectionName.aggregate([{ $match: { values: { $elemMatch: { name: "total", value: { $gte: 100 } } } } },
{
$addFields: {
totalValue: {
$arrayElemAt: [{
$filter: {
input: "$values",
as: "item",
cond: { $eq: ["$$item.name", 'total'] }
}
}, 0]
}
}
},
{
$project: {
values: {
$filter: {
input: "$values",
as: "value",
cond: {
$or: [
{ $cond: [{ $eq: ["$$value.name", "value1"] }, { $gt: [{ $divide: ["$$value.value", '$totalValue.value'] }, 0.5] }, false] },
{ $cond: [{ $eq: ["$$value.name", "value2"] }, { $gt: [{ $divide: ["$$value.value", '$totalValue.value'] }, 0.25] }, false] }
]
}
}
}, object_name: 1
}
}, {
$match: {
$expr: { $gt: [{ $size: "$values" }, 1] }
}
}, { $project: { object_name: 1, _id: 0 } }])
Collection Data :
/* 1 */
{
"_id" : ObjectId("5e20bd94d02e05b694d55fa5"),
"object_name" : "Example",
"values" : [
{
"name" : "value1",
"value" : 1
},
{
"name" : "value2",
"value" : 10
},
{
"name" : "total",
"value" : 105
},
{
"name" : "total1",
"value" : 105
}
]
}
/* 2 */
{
"_id" : ObjectId("5e20bdb1d02e05b694d56490"),
"object_name" : "Example2",
"values" : [
{
"name" : "value1",
"value" : 1
},
{
"name" : "value2",
"value" : 10
},
{
"name" : "total",
"value" : 5
},
{
"name" : "total1",
"value" : 5
}
]
}
/* 3 */
{
"_id" : ObjectId("5e20d1b7d02e05b694d7c57a"),
"object_name" : "Example3",
"values" : [
{
"name" : "value1",
"value" : 100
},
{
"name" : "value2",
"value" : 100
},
{
"name" : "total",
"value" : 200
},
{
"name" : "total1",
"value" : 205
}
]
}
/* 4 */
{
"_id" : ObjectId("5e20d1cad02e05b694d7c71c"),
"object_name" : "Example4",
"values" : [
{
"name" : "value1",
"value" : 200
},
{
"name" : "value2",
"value" : 40
},
{
"name" : "total",
"value" : 200
},
{
"name" : "total1",
"value" : 205
}
]
}
/* 5 */
{
"_id" : ObjectId("5e20d1e2d02e05b694d7c933"),
"object_name" : "Example5",
"values" : [
{
"name" : "value1",
"value" : 150
},
{
"name" : "value2",
"value" : 100
},
{
"name" : "total",
"value" : 200
},
{
"name" : "total1",
"value" : 205
}
]
}
Result :
/* 1 */
{
"object_name" : "Example5"
}
You may convert your array into object with $arrayToObject operator and add tmp field to have easy access to value1, value2, total values
db.collection.aggregate([
{
$addFields: {
tmp: {
$arrayToObject: {
$map: {
input: "$values",
as: "value",
in: {
k: "$$value.name",
v: "$$value.value"
}
}
}
},
name: 1
}
},
{
$match: {
$expr: {
$and: [
{
$gt: [
{
$divide: [
"$tmp.value1",
"$tmp.total"
]
},
0.5
]
},
{
$gt: [
{
$divide: [
"$tmp.value2",
"$tmp.total"
]
},
0.25
]
},
{
$gt: [
"$tmp.total",
100
]
}
]
}
}
},
{
$project: {
tmp: 0
}
}
])
MongoPlayground

Mongodb Group by get $max and count of max in value and percent of that group

I need to a group by on x field and get the max value of other fields. Yes, using $max we can get max repetitive value. But, I also need to get count $max value in percent/count too. In other words, how many times this $max value exist in that group. Kindly help.
example:
db.getCollection("test").aggregate(
[
{ "$match" : { "doc_id" : 1.0 } },
{ "$group" : {
"_id" : { "name" : "$name" },
"total" : { "$sum" : "$amount" },
"l1_max" : { "$max" : "$l1" }
}
},
]
);
Here, I am getting l1_max = 'Computer' . But, I need it as 'Computer - (30%) Total 4/12'
Updated: 20/10/2019
#mickl : Thanks for the answer.
The field l1 is actually a referenced field. In normal find/project or mongoose populate(), it helps to get fields from other collection. Example:
if l1 is of type ObjectId then,
l1: {
_id, "4343434343sdsdsY",
name: "IT"
}
So l1.name will fetch name field from another collection in project/populate function.
I executed following code:
db.getCollection("test").aggregate(
[
{ "$match" : { "doc_id" : 1.0 } },
{ "$group" : {
"_id" : { "name" : "$name" },
"total" : { "$sum" : "$amount" },
"count": { '$sum': 1 },
"l1_max" : { "$max" : "$l1" },
"l1_values": { $push: "$l1" }
}
},
{
$project: {
_id: 1,
total: 1,
l1 : {"_id": "$l1_max", "count": "$count", "percent": { $divide: [ { $size: { $filter: { input: "$l1_values", cond: { $eq: [ "$$this", "$l1_max" ] } } } },"$count"]}}
}
}
]
);
Answer is like below: But I also need referenced name field too.
{
"_id" : {
"name" : "xzy"
},
"total" : 35.0,
"l1" : {
"_id" : "4343920239201W",
"name" : "IT", // **MISSING**
"count" : 4.0,
"percent" : 0.25
}
}
Hope I was clear this time.
You need to capture all l1 values in your group and the calculate the percent using $divide, $filter and $size:
db.getCollection("test").aggregate(
[
{ "$match" : { "doc_id" : 1.0 } },
{ "$group" : {
"_id" : { "name" : "$name" },
"total" : { "$sum" : "$amount" },
"l1_max" : { "$max" : "$l1" },
"l1_values": { $push: "$l1" }
}
},
{
$project: {
_id: 1,
total: 1,
l1_max: 1,
l1_perc: {
$divide: [
{ $size: { $filter: { input: "$l1_values", cond: { $eq: [ "$$this", "$l1_max" ] } } } },
{ $size: "$l1_values" }
]
}
}
}
]
);
Mongo Playground

Conditional lookup for nested localfield

I want to get user's friends and their information. But only wanna join for users where "status" equals to 2.
Here is my object
{
"user_id" : ObjectId("5d2f574b1c27807fd7eb133d"),
"relationship" : [
{
"user_id" : ObjectId("5d2f57661c27807fd7eb133e"),
"status" : 2
},
{
"user_id" : ObjectId("5d2f57c01c27807fd7eb133f"),
"status" : 1
}
]
}
My query
db.getCollection('relationships').aggregate([
{$match: {
user_id: ObjectId("5d2f574b1c27807fd7eb133d")}
},
{ $lookup: {
from: "users",
as: "users",
let: { id: "$_id" },
pipeline: [
{ $match: {
$expr: { $and: [
{ $eq: [ "$relationship.user_id", "$$id" ] },
{ $eq: [ "$relationship.status", 2 ] }
] }
} }
],
} }
])
expected output
{
"user_id" : ObjectId("5d2f574b1c27807fd7eb133d"),
"friends" : [
{
"_id" : ObjectId("5d2f57661c27807fd7eb133e"),
"name" : "Mike"
}
]
}
What's the proper way to do this ?

How can I get this result in mongodb?

I have this data structure:
"_id" : "121212",
"terms" : [
{
"term" : "hi",
"tf" : 2
},
{
"term" : "you",
"tf" : 1
}
]
}
and making this query:
db.foo.aggregate( [
{
$match : { _id : "121212" }
},
{
$project:{ terms:1 }
},
{
$unwind: "$terms"
}
]).pretty();
I have come to get this result in my db:
{
"_id" : "121212",
"terms" : {
"term" : "hi",
"tf" : 2
}
}
{
"_id" : "121212",
"terms" : {
"term" : "you",
"tf" : 1
}
}
but is there any way to get a result like this?:
{
"_id" : "121212",
"term" : "hi",
"tf" : 2
}
{
"_id" : "121212",
"term" : "you",
"tf" : 1
}
I have tried to build the query with $ replaceRoot: {newRoot: "$ terms"}, but after I can't select the _id field anymore.
Well, you can use the $map and $mergeObjects to do this beautifully.
[
{ "$match":{"_id":"121212"}},
{
"$addFields":{
"terms":{
"$map":{
"input":"$terms",
"in":{
"$mergeObjects":[
"$$this",
{
"_id":"$_id"
}
]
}
}
}
}
}
]
If you really need to deconstruct the "terms" array, then add the $unwind: "$terms" to the pipeline.
You can achieve by using $project stage at the end of the pipeline
db.foo.aggregate([
{ "$match" : { "_id": "121212" } },
{ "$unwind": "$terms" },
{ "$project": { "term": "$terms.term", "tf": "$terms.tf" }}
])
Output
[
{
"_id": "121212",
"term": "hi",
"tf": 2
},
{
"_id": "121212",
"term": "you",
"tf": 1
}
]
Check it here
You need to use $mergeObjects inside $replaceRoot:
db.foo.aggregate( [
{
$match : { _id : "121212" }
},
{
$project:{ terms:1 }
},
{
$unwind: "$terms"
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [ { _id: "$_id" }, "$terms" ]
}
}
}
]).pretty();
Just to complete the range of options:
db.foo.aggregate([
{ "$match" : { "_id": "121212" } }, // filter by "_id"
{ "$addFields": { "terms._id": "$_id" } }, // copy "_id" field into terms
{ "$unwind": "$terms" }, // flatten the "terms" array
{ "$replaceRoot": { "newRoot": "$terms" } } // move the contents of the "terms" field up to the root level
])