MongoDB Query that will $project document values in horizontal format - mongodb

Im trying to create a query that will project or show the field in HORIZONTAL format. Below is my sample collection and the expected output. BTW, Im also using jaspersoft studio to create the report but i think my output should be done in mongodb query. Hope someone can help. Thanks!
"_id" : ObjectId("60ddc6f44c893c117141e9b9"),
"observationvalues" : [
{
"_id" : ObjectId("60ddc6f44c893c117141e9c3"),
"name" : "Systolic BP",
"resultvalue" : "88.00"
},
{
"_id" : ObjectId("60ddc6f44c893c117141e9c2"),
"name" : "Diastolic BP",
"resultvalue" : "66.00"
},
{
"_id" : ObjectId("60ddc6f44c893c117141e9c1"),
"name" : "Weight",
"resultvalue" : "90.00"
},
{
"_id" : ObjectId("60ddc6f44c893c117141e9c5"),
"name" : "Height",
"resultvalue" : null
}
],
"createdat" : ISODate("2021-07-01T13:45:24.679Z"),
"modifiedat" : ISODate("2021-07-01T13:45:24.679Z"),
"statusflag" : "A"
"_id" : ObjectId("60ddc6f44c893c117141e8b8"),
"observationvalues" : [
{
"_id" : ObjectId("60ddc6f44c893c117141e9b3"),
"name" : "Systolic BP",
"resultvalue" : "84.00"
},
{
"_id" : ObjectId("60ddc6f44c893c117141e9b2"),
"name" : "Diastolic BP",
"resultvalue" : "63.00"
},
{
"_id" : ObjectId("60ddc6f44c893c117141e9b1"),
"name" : "Weight",
"resultvalue" : "99.00"
},
{
"_id" : ObjectId("60ddc6f44c893c117141e9b5"),
"name" : "Height",
"resultvalue" : 172.00
}
],
"createdat" : ISODate("2021-07-02T13:45:24.679Z"),
"modifiedat" : ISODate("2021-07-02T13:45:24.679Z"),
"statusflag" : "A"
"_id" : ObjectId("60ddc6f44c893c117141e7b7"),
"observationvalues" : [
{
"_id" : ObjectId("60ddc6f44c893c117141e9a3"),
"name" : "Systolic BP",
"resultvalue" : "81.00"
},
{
"_id" : ObjectId("60ddc6f44c893c117141e9a2"),
"name" : "Diastolic BP",
"resultvalue" : "65.00"
},
{
"_id" : ObjectId("60ddc6f44c893c117141e9a1"),
"name" : "Weight",
"resultvalue" : "96.00"
},
{
"_id" : ObjectId("60ddc6f44c893c117141e9a5"),
"name" : "Height",
"resultvalue" : 165.00
}
],
"createdat" : ISODate("2021-07-03T13:45:24.679Z"),
"modifiedat" : ISODate("2021-07-03T13:45:24.679Z"),
"statusflag" : "A",
The expected Output will be:
{"createdat" : "2021-07-01T13:45:24.679Z", "Systolic BP" : 88.00 ,"Diastolic BP" : 66.00 ,"Weight": 90.00 ,"Height":null }
{"createdat" : "2021-07-01T13:45:24.679Z", "Systolic BP" : 84.00 ,"Diastolic BP" : 63.00 ,"Weight": 99.00 ,"Height":172.00 }
{"createdat" : "2021-07-03T13:45:24.679Z", "Systolic BP" : 81.00 ,"Diastolic BP" : 65.00 ,"Weight": 96.00 ,"Height":165.00 }
I have created my query but it is not having the output in one line grouped for each createdat field
{
$project : {
_id: {
"systolic": {"$cond": [ { "$eq": ["$name","Systolic BP" ] }, "$resultvalue","" ] },
"diastolic": {"$cond": [ { "$eq": ["$name","Diastolic BP" ] }, "$resultvalue","" ] },
"weight": {"$cond": [ { "$eq": ["$name","Weight" ] }, "$resultvalue","" ] },
"height": {"$cond": [ { "$eq": ["$name","Height" ] }, "$resultvalue","" ] },
"create": "$createdat"
}
}
},

Your expected data output could be done like this: playground
[
{
"$project": {
"createdat": 1,
"rows": {
"$arrayToObject": {
"$map": {
"input": "$observationvalues",
"in": {
"k": "$$this.name",
"v": {
"$convert": {
"input": "$$this.resultvalue",
"to": "double",
"onError": null,
"onNull": null
}
}
}
}
}
}
}
},
{
"$addFields": {
"rows.create": "$createdat"
}
},
{
"$replaceRoot": {
"newRoot": "$rows"
}
},
{
"$match": {
"Systolic BP": 84.00
}
}
]
To get them in one line you should look for some JSON formater.

Related

Finding/Counting Duplicate Values in Array in MongoDB

I am new to the mongo database. Using Robo3t software
I have to find out duplicate values inside an array based on channel_id.
I did a research and found that aggregation needs to be used to do grouping and find respective count.
I have developed the following query but results are not as expected.
Sample Documents:
{
"_id" : ObjectId("59b674d141b47e5401897d31"),
"subscribed_channels" : [
{
"channel_id" : "1001",
"channel_name" : "StarPlus",
"channelPrice":"100"
},
{
"channel_id" : "1002",
"channel_name" : "StarGold",
"channelPrice":"75"
},
{
"channel_id" : "1001",
"channel_name" : "StarPlus",
"channelPrice":"100"
},
{
"channel_id" : "1003",
"channel_name" : "SetMax",
"channelPrice":"80"
}
],
"viewer_account_id" : "59b6745b41b47e5401143b3d",
"public_id_type" : "PHONE_NUMBER",
"viewer_id" : "+919322264403",
"role" : "CONSUMER",
"active" : true,
"date_time_created" : NumberLong(1505129681330),
"date_time_modified" : NumberLong(1569320824387)
}
{
"_id" : ObjectId("59b674d141b47e5401897d31"),
"subscribed_channels" : [
{
"channel_id" : "1001",
"channel_name" : "StarPlus",
"channelPrice":"100"
},
{
"channel_id" : "1002",
"channel_name" : "StarGold",
"channelPrice":"75"
},
{
"channel_id" : "1001",
"channel_name" : "StarPlus",
"channelPrice":"100"
},
{
"channel_id" : "1001",
"channel_name" : "StarPlus",
"channelPrice":"100"
}
],
"viewer_account_id" : "59b6745b41b47e5401143c56",
"public_id_type" : "PHONE_NUMBER",
"viewer_id" : "+919322264404",
"role" : "CONSUMER",
"active" : true,
"date_time_created" : NumberLong(1505129681330),
"date_time_modified" : NumberLong(1569320824387)
}
Above are just 2 records of document viewers
Query :
db.getCollection('viewers').aggregate([
{
"$group" :
{_id:{
//viewer_id:"$consumer_id",
enterprise_id:"$subscribed_channels.channel_id",
},
"viewer_id": {
$first: "$viewer_id"
},
count:{$sum:1}
}},
{
"$match": {"count": { "$gt": 1 }}
}
])
Actual Output :
{
"_id" : {
"enterprise_id" : [
"1001",
"1001",
"1002",
"1003"
]
},
"consumer_id" : "+919322264403",
"count" : 2.0
}
{
"_id" : {
"enterprise_id" : [
"1001",
"1002",
"1001",
"1001
]
},
"consumer_id" : "+919322264404",
"count" : 2.0
}
Expected Output :
I want to group based on subscribed_channels.channel_id and get a count respectively
{
"_id" : {
"enterprise_id" : [
"1001",
"1001",
"1002",
"1003"
]
},
"consumer_id" : "+919322264403",
"count" : 2.0
}
{
"_id" : {
"enterprise_id" : [
"1001",
"1001",
"1001",
"1002
]
},
"consumer_id" : "+919322264404",
"count" : 3.0
}
Grouping is not happening based on channel_id, also the count is incorrect.
The count is not even giving me no of channel-id subscribed, also not giving duplicate channel_ids.
Please guide me in building a query that gives the correct result.
Try below query :
Query :
db.collection.aggregate([
/** project only needed fields & transform fields as you like */
{
$project: {
customer_id: "$viewer_id",
enterprise_id: "$subscribed_channels.channel_id",
count: {
/** Subtract size of original array & newly formed array which has unique values to get count of duplicates */
$subtract: [
{
$size: "$subscribed_channels.channel_id" // get size of original array
},
{
$size: {
$setUnion: ["$subscribed_channels.channel_id", []] // This will give you an array with unique elements & get size of it
}
}
]
}
}
}
]);
Test : MongoDB-Playground

Group and Merge array of objects

I am struggling around with the aggregation pipeline feature from MongoDB.
So far the output for one result looks like this:
{
"type": "inbound",
"sender": "postAG",
"receiver": "maxMusterMan",
"datetime": "20191125",
"info": [
{
"q": "A",
"value": "5",
"name": null,
"plz": 1234
},
{
"q": "B",
"value": "AS",
"name": "ABS",
"plz": null
},
{
"q": "A",
"value": "5",
"name": "aa",
"plz": null
},
... more objects
]
}
The final result should look like:
{
"type": "inbound",
"sender": "postAG",
"receiver": "maxMusterMan",
"datetime": "20191125",
"info": [
{
"q": "A",
"value": "0",
"name": "aa",
"plz": 1234
},
{
"q": "B",
"value": "AS",
"name": "ABS"
}
]
}
So in a nutshell, I want to group the values from the array field info by the "q" field and merge the objects (newer one overwrites the old value).
Further I would like to remove all the values with value "" or null;
There are more fields in the real payload, so I would like to avoid to add a $cond for each field of the object.
Some approaches so far from my side:
for the cleanup, use a UDF, but this is not possible in the pipeline.
use map-reduce for the group and merge, not available in the pipeline.
Please consider that the input file is the output from the several pipeline steps.
So I can not just use map-reduce alone, first I need the pipeline too.
My idea was to create two views, first will do the pipeline stuff and second map-reduce, is this a good solution?
Thx
Andreas
I didn't really understand from your explanation if you can or cannot use map-reduce.
However assuming you can't and you have to 'concat' the pipelines there is no 'generic' workaround for multiple fields - you have to create a condition for each in the pipeline.
With that said here is a working pipeline:
db.collection.aggregate(
[
{
"$unwind" : "$info"
},
{
"$group" : {
"_id" : "$info.q",
"type" : {
"$first" : "$type"
},
"sender" : {
"$first" : "$sender"
},
"receiver" : {
"$first" : "$receiver"
},
"datetime" : {
"$first" : "$datetime"
},
"values" : {
"$push" : "$info.value"
},
"names" : {
"$push" : "$info.name"
},
"plz" : {
"$push" : "$info.plz"
}
}
},
{
"$project" : {
"_id" : 1.0,
"type" : 1.0,
"sender" : 1.0,
"receiver" : 1.0,
"datetime" : 1.0,
"values" : {
"$filter" : {
"input" : "$values",
"as" : "curr",
"cond" : {
"$or" : [
{
"$ne" : [
"$$curr",
null
]
},
{
"$ne" : [
"$$curr",
""
]
}
]
}
}
},
"names" : {
"$filter" : {
"input" : "$names",
"as" : "curr",
"cond" : {
"$or" : [
{
"$ne" : [
"$$curr",
null
]
},
{
"$ne" : [
"$$curr",
""
]
}
]
}
}
},
"plz" : {
"$filter" : {
"input" : "$plz",
"as" : "curr",
"cond" : {
"$or" : [
{
"$ne" : [
"$$curr",
null
]
},
{
"$ne" : [
"$$curr",
""
]
}
]
}
}
}
}
},
{
"$project" : {
"sender" : 1.0,
"receiver" : 1.0,
"datetime" : 1.0,
"type" : 1.0,
"_id" : 1.0,
"value" : {
"$cond" : {
"if" : {
"$gt" : [
{
"$size" : "$values"
},
0.0
]
},
"then" : {
"$arrayElemAt" : [
"$values",
-1.0
]
},
"else" : null
}
},
"name" : {
"$cond" : {
"if" : {
"$gt" : [
{
"$size" : "$names"
},
0.0
]
},
"then" : {
"$arrayElemAt" : [
"$names",
-1.0
]
},
"else" : null
}
},
"plz" : {
"$cond" : {
"if" : {
"$gt" : [
{
"$size" : "$plz"
},
0.0
]
},
"then" : {
"$arrayElemAt" : [
"$plz",
-1.0
]
},
"else" : null
}
}
}
},
{
"$addFields" : {
"infoArray" : [
{
"k" : "type",
"v" : "$_id"
},
{
"k" : "value",
"v" : "$value"
},
{
"k" : "name",
"v" : "$name"
},
{
"k" : "plz",
"v" : "$plz"
}
]
}
},
{
"$addFields" : {
"info" : {
"$arrayToObject" : {
"$filter" : {
"input" : "$infoArray",
"as" : "curr",
"cond" : {
"$ne" : [
"$$curr.v",
null
]
}
}
}
}
}
},
{
"$group" : {
"_id" : null,
"type" : {
"$first" : "$type"
},
"sender" : {
"$first" : "$sender"
},
"receiver" : {
"$first" : "$receiver"
},
"datetime" : {
"$first" : "$datetime"
},
"info" : {
"$push" : "$info"
}
}
}
]
)

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
}

mongodb and aggregate multiple items

i have this code
> db.attributes.aggregate([{ $match: {"name.en-UK": "Size"} },{ $unwind : "$values" },{ $project: { "code" : "$values.code", "name" : "$values.name", "_id": "$values._id"} },{ $match: {"name.en-UK": "S"}}])
{
"result" : [
{
"_id" : ObjectId("53b2c644d901f244965f74e3"),
"code" : 0,
"name" : {
"en-UK" : "S"
}
},
{
"_id" : ObjectId("53b2c644d901f244965f74e4"),
"code" : 1,
"name" : {
"en-UK" : "S"
}
}
],
"ok" : 1
}
how do i change it so that it returns only "code" == 0 with "name" == "en-UK" : "S"
i tried this:
> db.attributes.aggregate([{ '$match': {"name.en-UK": "Size"} },
... { '$unwind' : "$values" },
... { '$project': { "code" : "$values.code", "valueId": "$values._id"} },
... { '$match': {
... $and: [
... {"name.en-UK": "S"},
... {"code": 0}
... ]
... }
... }])
{ "result" : [ ], "ok" : 1 }
but is not working!
and advice much appreciated
it seems i need to add what to select, so { $project: { "code" : "$values.code", "name" : "$values.name", "_id": "$values._id"} } must include the name
> db.attributes.aggregate([{ '$match': {"name.en-UK": "Size"} }, { '$unwind' : "$values" }, { $project: { "code" : "$values.code", "name" : "$values.name", "_id": "$values._id"} }, { '$match': { $and: [ {"name.en-UK": "S"}, {"code": 0} ] } }])
{
"result" : [
{
"_id" : ObjectId("53b2c644d901f244965f74e3"),
"code" : 0,
"name" : {
"en-UK" : "S"
}
}
],
"ok" : 1
}