match element in the array with aggregation - mongodb

i have mongo db collection the follwing structure
{
{
"_id" : ObjectId("63e37afe7a3453d5014c011b"),
"schemaVersion" : NumberInt(1),
"Id" : "ObjectId("63e37afe7a3453d5014c0112")",
"Id1" : "ObjectId("63e37afe7a3453d5014c0113")",
"Id2" : "ObjectId("63e37afe7a3453d5014c0114")",
"collectionName" : "Country",
"List" : [
{
"countryId" : NumberInt(1),
"name" : "Afghanistan",
},{
"countryId" : NumberInt(1),
"name" : "India",
},
{
"countryId" : NumberInt(1),
"name" : "USA",
}
}
i need to match the value with id, id1, id2, collectionName and name in the list to get country id for example if match the below value
"Id" : "ObjectId("63e37afe7a3453d5014c0112")",
"Id1" : "ObjectId("63e37afe7a3453d5014c0113")",
"Id2" : "ObjectId("63e37afe7a3453d5014c0114")",
"collectionName" : "Country",
"name" : "Afghanistan",
i need result
{
"countryId" : 1,
"name" : "Afghanistan",
}
i tried like below
db.country_admin.aggregate([
{ $match: { collectionName: "Country" } },
{ $unwind : '$countryList' },
{ $project : { _id : 0, 'countryList.name' : 1, 'countryList.countryId' : 1 } }
]).pretty()
and i have following output
[
{
"List" : {
"countryId" : 1.0,
"name" : "Afghanistan"
}
},
{
"List" : {
"countryId" : 2.0,
"name" : "india"
}
},
{
"List" : {
"countryId" : 3.0,
"name" : "USA"
}
}]```

You can try using $filter to avoid $unwind like this example:
First $match by your desired condition(s).
Then $filter and get the first element (as "List.name": "Afghanistan" is used into $match stage there will be at least one result).
And output only values you want using $project.
db.collection.aggregate([
{
"$match": {
"Id": ObjectId("63e37afe7a3453d5014c0112"),
"Id1": ObjectId("63e37afe7a3453d5014c0113"),
"Id2": ObjectId("63e37afe7a3453d5014c0114"),
"collectionName": "Country",
"List.name": "Afghanistan",
}
},
{
"$project": {
"country": {
"$arrayElemAt": [
{
"$filter": {
"input": "$List",
"cond": {
"$eq": [
"$$this.name",
"Afghanistan"
]
}
}
},
0
]
}
}
},
{
"$project": {
"_id": 0,
"countryId": "$country.countryId",
"name": "$country.name"
}
}
])
Example here
By the way, using $unwind is also possible and you can check this example

Related

How to change the type of field in an array of sub-documents

I have a collection of documents, each of which possesses an array of subdocuments (ranging from 1-10,000 objects). In a small portion of these documents, a field in the arrayed sub-documents has been set to a string instead of an integer and I need to convert these values to an Integer
Here is a structural sample. Note that the DB Admin built the database and collection names with a '.' notation which has complicated some of my work thus far:
Collection Name: "employee.roster"
{
"_id" : ObjectId("5f11d4c28663f32e940696e0"),
"PdfId" : NumberInt(100),
"Staff" : [
{
"StaffId" : NumberInt(1),
"StaffName" : "John Doe"
},
{
"StaffId" : NumberInt(2),
"StaffName" : "John Smith"
},
{
"StaffId" : "3",
"StaffName" : "John Jones"
}
]
}
{
"_id" : ObjectId("5f11d4c28663f32e940696e1"),
"PdfId" : NumberInt(110),
"Staff" : [
{
"StaffId" : "4",
"StaffName" : "Bob Loblaw"
},
{
"StaffId" : NumberInt(5),
"StaffName" : "Edward Nigma"
},
{
"StaffId" : "6",
"StaffName" : "Hugh Mongus"
}
]
}
I have tried a variety of methods without success. Based on other posts, I thought something like this should work but I've generated nothing but errors:
db.getCollection("staff.roster").update(
{},
[{ $set: { "Staff.$[elem].StaffId": { $toInt: "$Staff.$[elem].StaffId" } } }],
{ "arrayFilters": [{ "elem.StaffId": { $type: 2 } } ], "multi": true }
)
ERROR MESSAGE:
WriteResult({
"nMatched" : 0,
"nUpserted" : 0,
"nModified" : 0,
"writeError" : {
"code" : 9,
"errmsg" : "arrayFilters may not be specified for pipeline-syle updates"
}
})
I've also tried this but I believe my notation is wrong because of the sub-documents:
db.getCollection("staff.roster").find( { "Staff.StaffId" : { $type : 2 } } ).forEach( function (x) {
x."Staff.StaffId" = new NumberInt(x."Staff.StaffId");
db.getCollection("staff.roster").save(x);
});
My output should look like this:
{
"_id" : ObjectId("5f11d4c28663f32e940696e0"),
"PdfId" : NumberInt(100),
"Staff" : [
{
"StaffId" : NumberInt(1),
"StaffName" : "John Doe"
},
{
"StaffId" : NumberInt(2),
"StaffName" : "John Smith"
},
{
"StaffId" : NumberInt(3),
"StaffName" : "John Jones"
}
]
}
{
"_id" : ObjectId("5f11d4c28663f32e940696e1"),
"PdfId" : NumberInt(110),
"Staff" : [
{
"StaffId" : NumberInt(4),
"StaffName" : "Bob Loblaw"
},
{
"StaffId" : NumberInt(5),
"StaffName" : "Edward Nigma"
},
{
"StaffId" : NumberInt(6),
"StaffName" : "Hugh Mongus"
}
]
}
You can use update with aggregation pipeline starting from MongoDB 4.2,
$map to iterate loop of Staff array, change the type of StaffId and merge objects with other fields using $mergeObjects
db.getCollection("staff.roster").update({},
[{
$set: {
Staff: {
$map: {
input: "$Staff",
in: {
$mergeObjects: [
"$$this",
{ StaffId: { $toInt: "$$this.StaffId" } }
]
}
}
}
}
}]
)
Playground

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

Querying Properties Array mongodb

I am trying to get building by its id:
I have a collection called buildings:
{
"_id" : ObjectId("5b3b1cc79c23061e4d4634e4"),
"buildings" : [
{
"id" : 0,
"name" : "Farm",
"description" : "Best farm of all times",
"img" : "http://i.hizliresim.com/yq5g57.png",
"wood" : "50",
"stone" : "10"
},
{
"id" : 1,
"name" : "Storage",
"description" : "Store your resources.",
"img" : "http://i.hizliresim.com/yq5g47.png",
"wood" : "100",
"stone" : "200"
}
]
}
For example with id 0,i would like to get data of Farm.
I tried this:
db.getCollection('buildings').find({"buildings.id":0})
not working
sample output :
{
"id" : 0,
"name" : "Farm",
"description" : "Best farm of all times",
"img" : "http://i.hizliresim.com/yq5g57.png",
"wood" : "50",
"stone" : "10"
}
Tried:
var data = Buildings.find({},{buildings:{$elemMatch:{id:0}}}).fetch();
console.log(JSON.stringify(data));
result:(all data)
[{"_id":{"_str":"5b3b1cc79c23061e4d4634e4"},"buildings":[{"id":0,"name":"Farm","description":"Best farm of all times","img":"http://i.hizliresim.com/yq5g57.png","wood":"50","stone":"10"},{"id":1,"name":"Storage","description":"Store your resources.","img":"http://i.hizliresim.com/yq5g47.png","wood":"100","stone":"200"}]}]
You can use $filter aggregation to exclude the unwanted elements from the array
db.collection.aggregate([
{ "$match": { "buildings.id": 0 }},
{ "$project": {
"shapes": {
"$arrayElemAt": [
{ "$filter": {
"input": "$buildings",
"as": "building",
"cond": {
"$eq": [
"$$building.id",
0
]
}
}},
0
]
},
"_id": 0
}}
])
Try for this
db.getCollection("collectionName").find({buildings: {"$elemMatch": {"id" : "0"}}})
Here the find method will look(cursor) for the data with buildings and id=0
db.collection.find({
buildings: {
$elemMatch: {
id: 0
}
}
}, {
'buildings.$': 1
})

How sort according to given order and calculation 1st document with second one in MongoDB?

I want to get which members not present when state change and which member is present.
My given array for state order like:
[{order:1,text:'CS'}, {order:2,text:'IP'}, {order:3,text:'AC'}]
so I want to sort according to this array want to perform some operation with each pare of documents
My documents like:
{
"count" : 2,
"state" : "CS",
"members" : [
{
"email" : "builuu1998#gmail.com",
"date" : ISODate("2016-12-24T03:39:05.720Z")
},
{
"email" : "bactv.hn#gmail.com",
"date" : ISODate("2016-12-25T02:32:48.698Z")
}
]
},
{
"count" : 1,
"state" : "AC",
"members" : [
{
"email" : "builuu1998#gmail.com",
"date" : ISODate("2016-12-24T03:39:05.720Z")
}
]
},
{
"count" : 3,
"state" : "IP",
"members" : [
{
"email" : "builuu1998#gmail.com",
"date" : ISODate("2016-12-24T03:39:05.720Z")
},
{
"email" : "bactv.hn#gmail.com",
"date" : ISODate("2016-12-25T02:32:48.698Z")
},
{
"email" : "abc.hn#gmail.com",
"date" : ISODate("2016-12-25T02:32:48.698Z")
}
]
}
So I want to know which members are not present from one state to another state and which members are present .
In my exam 1st state to 2nd state means (CS - IP): present 2 members and not present 1 member
{
"state": "CS_IP",
"present": [
{
"email" : "builuu1998#gmail.com",
"date" : ISODate("2016-12-24T03:39:05.720Z")
},
{
"email" : "bactv.hn#gmail.com",
"date" : ISODate("2016-12-25T02:32:48.698Z")
}
],
"notPresent": [
{
"email" : "abc.hn#gmail.com",
"date" : ISODate("2016-12-25T02:32:48.698Z")
}
]
}
and 2nd state to 3rd state means (IP - AC): present 1 members and not present 2 member
{"state": "IP_AC",
"present": [
{
"email" : "builuu1998#gmail.com",
"date" : ISODate("2016-12-24T03:39:05.720Z")
}
],
"notPresent": [
{
"email" : "bactv.hn#gmail.com",
"date" : ISODate("2016-12-25T02:32:48.698Z")
},
{
"email" : "abc.hn#gmail.com",
"date" : ISODate("2016-12-25T02:32:48.698Z")
}
]}
How can I achieve this using aggregate query because I need some aggregate operation after this stage complete
You can perform one aggregation request per "couple" of state like this for ["IP", "AC"] :
db.exams.aggregate([{
$match: {
"state": {
$in: ["IP", "AC"]
}
}
}, {
"$group": {
"_id": 1,
"group1": { "$first": "$members" },
"group2": { "$last": "$members" }
}
}, {
"$project": {
"present": { "$setIntersection": ["$group1", "$group2"] },
"notPresent": {
$setUnion: [
{ "$setDifference": ["$group1", "$group2"] },
{ "$setDifference": ["$group2", "$group1"] }
]
}
}
}])
Note that this will only work for 2 elements to match (here ["IP", "AC"]) since we have to create two new fields group1 & group2 to $setIntersection and $setDifference (as there is no $group with $setIntersection and $setDifference)
The query above will give :
{
"_id": 1,
"present": [
{ "email": "builuu1998#gmail.com", "date": ISODate("2016-12-24T03:39:05.720Z") }
],
"notPresent": [
{ "email": "abc.hn#gmail.com", "date": ISODate("2016-12-25T02:32:48.698Z") },
{ "email": "bactv.hn#gmail.com", "date": ISODate("2016-12-25T02:32:48.698Z") }
]
}

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
}