mongodb aggregation match multiple $and on the same field - mongodb

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
}

Related

MongoDB Query that will $project document values in horizontal format

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.

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"
}
}
}
]
)

how to sort an array in a nested array which is located under an object in mongodb

I have a collection data like below.
{
"name": "Devices",
"exten": {
"parameters": [{
"name": "Date",
"value": ["5","2"]
}, {
"name": "Time",
"value": ["2"]
}, {
"name": "Season",
"value": ["6"]
}
]
}
}
I want to take all data which is name "Devices" and sort by first index of "Value" which is parameter name is "Date"
ex: mongo will get
name = "devices"
exten.parameters.name = "Date"
will sort it by
exten.parameters.value[0]
in this example it will be sorted by "5".
below query returns 0 record.
db.brand.aggregate(
{ $match: {
"name" : "Devices"
}},
{ $unwind: "$exten.parameters" },
{ $match: {
'exten.parameters.name': 'Date'
}},
{ $sort: {
'exten.parameters.value': -1
}}
)
The following query can get us the expected output:
db.collection.aggregate([
{
$match:{
"name":"Devices"
}
},
{
$unwind:"$exten.parameters"
},
{
$match:{
"exten.parameters.name":"Date"
}
},
{
$project:{
"name":1,
"exten":1,
"firstParam":{
$arrayElemAt:["$exten.parameters.value",0]
}
}
},
{
$sort:{
"firstParam":1
}
},
{
$project:{
"firstParam":0
}
}
]).pretty()
Data set:
{
"_id" : ObjectId("5da02fb86472ba670fd8c159"),
"name" : "Devices",
"exten" : {
"parameters" : [
{
"name" : "Date",
"value" : [
"5",
"2"
]
},
{
"name" : "Date",
"value" : [
"2",
"7"
]
},
{
"name" : "Time",
"value" : [
"2"
]
},
{
"name" : "Season",
"value" : [
"6"
]
}
]
}
}
Output:
{
"_id" : ObjectId("5da02fb86472ba670fd8c159"),
"name" : "Devices",
"exten" : {
"parameters" : {
"name" : "Date",
"value" : [
"2",
"7"
]
}
}
}
{
"_id" : ObjectId("5da02fb86472ba670fd8c159"),
"name" : "Devices",
"exten" : {
"parameters" : {
"name" : "Date",
"value" : [
"5",
"2"
]
}
}
}

MongoDB creates array of arrays in $group $push instead of flat array

I am trying to group a set of documents after an $unwind operation. My documents look like this:
{
"_id" : ObjectId("5cdb5b5acadf5100019da2f4"),
"allowedLocations" : [
{
"type" : "country",
"value" : "world",
"label" : "World"
}
],
"disallowedLocations" : [
{
"type" : "country",
"value" : "CF",
"label" : "Central African Republic"
},
{
"type" : "country",
"value" : "CN",
"label" : "China"
}
],
}
{
"_id" : ObjectId("5cdb5b5acadf5100019da2f4"),
"allowedLocations" : [
{
"type" : "country",
"value" : "US",
"label" : "United States of America"
}
],
"disallowedLocations" : [
{
"type" : "country",
"value" : "CA",
"label" : "Canada"
},
{
"type" : "country",
"value" : "MX",
"label" : "Mexico"
}
],
}
I want to group them by _id and then concatenate the allowedLocations and disallowedLocations arrays into one. The group stage in my pipeline looks like this:
{
"$group" : {
"_id" : "$_id",
"allowedLocations" : {
"$push" : "$allowedLocations"
},
"disallowedLocations" : {
"$push" : "disallowedLocations"
}
}
}
The problem is, the result I get is not a document with both arrays concatenated, but an array of arrays, each element of the array being the array of each document:
{
"_id" : ObjectId("5cdb5b5acadf5100019da2f4"),
"allowedLocations" : [
[
{
"type" : "country",
"value" : "US",
"label" : "United States of America"
}
],
[
{
"type" : "country",
"value" : "world",
"label" : "World"
}
],
],
"disallowedLocations" : [
[
{
"type" : "country",
"value" : "CF",
"label" : "Central African Republic"
},
{
"type" : "country",
"value" : "CN",
"label" : "China"
}
],
[
{
"type" : "country",
"value" : "CA",
"label" : "Canada"
},
{
"type" : "country",
"value" : "MX",
"label" : "Mexico"
}
]
}
}
Is there a way to produce a flat array with only objects as elements? I also tried with $concatArrays before the push but that creates more arrays inside the arrays.
Two solutions here. You can either run $unwind on both arrays to get single allowed and disallowed location per document and then run your $group stage:
db.col.aggregate([
{
$unwind: "$allowedLocations"
},
{
$unwind: "$disallowedLocations"
},
{
"$group" : {
"_id" : "$_id",
"allowedLocations" : {
"$addToSet" : "$allowedLocations"
},
"disallowedLocations" : {
"$addToSet" : "$disallowedLocations"
}
}
}
])
or you can run your $group first and then use $reduce to flatten allowedLocations and disallowedLocations:
db.col.aggregate([
{
"$group" : {
"_id" : "$_id",
"allowedLocations" : {
"$push" : "$allowedLocations"
},
"disallowedLocations" : {
"$push" : "$disallowedLocations"
}
}
},
{
$project: {
_id: 1,
allowedLocations: {
$reduce: {
input: "$allowedLocations",
initialValue: [],
in: { $concatArrays: [ "$$value", "$$this" ] }
}
},
disallowedLocations: {
$reduce: {
input: "$disallowedLocations",
initialValue: [],
in: { $concatArrays: [ "$$value", "$$this" ] }
}
}
}
}
])

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")
}
]
}