MongoDB multiple counts, single document, arrays - mongodb

I have been searching on stackoverflow and cannot find exactly what I am looking for and hope someone can help. I want to submit a single query, get multiple counts back, for a single document, based on array of that document.
My data:
db.myCollection.InsertOne({
"_id": "1",
"age": 30,
"items": [
{
"id": "1",
"isSuccessful": true,
"name": null
},{
"id": "2",
"isSuccessful": true,
"name": null
},{
"id": "3",
"isSuccessful": true,
"name": "Bob"
},{
"id": "4",
"isSuccessful": null,
"name": "Todd"
}
]
});
db.myCollection.InsertOne({
"_id": "2",
"age": 22,
"items": [
{
"id": "6",
"isSuccessful": true,
"name": "Jeff"
}
]
});
What I need back is the document and the counts associated to the items array for said document. In this example where the document _id = "1":
{
"_id": "1",
"age": 30,
{
"totalIsSuccessful" : 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1,
"totalNameNull": 2
}
}
I have found that I can get this in 4 queries using something like this below, but I would really like it to be one query.
db.test1.aggregate([
{ $match : { _id : "1" } },
{ "$project": {
"total": {
"$size": {
"$filter": {
"input": "$items",
"cond": { "$eq": [ "$$this.isSuccessful", true ] }
}
}
}
}}
])
Thanks in advance.

I am assuming your expected result is invalid since you have an object literal in the middle of another object and also you have totalIsSuccessful for id:1 as 2 where it seems they should be 3. With that said ...
you can get similar output via $unwind and then grouping with $sum and $cond:
db.collection.aggregate([
{ $match: { _id: "1" } },
{ $unwind: "$items" },
{ $group: {
_id: "_id",
age: { $first: "$age" },
totalIsSuccessful: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalNotIsSuccessful: { $sum: { $cond: [{ "$ne": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalSuccessfulNull: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", null ] }, 1, 0 ] } },
totalNameNull: { $sum: { $cond: [ { "$eq": [ "$items.name", null ]}, 1, 0] } } }
}
])
The output would be this:
[
{
"_id": "_id",
"age": 30,
"totalIsSuccessful": 3,
"totalNameNull": 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1
}
]
You can see it working here

Related

Filter documents that have id in another collection in MongoDB with aggregation framework

So I have two collection. collectionA and collectionB
collection A has following documents
db={
"collectiona": [
{
"_id": "6173ddf33ed09368a094e68a",
"title": "a"
},
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
],
"collectionb": [
{
"_id": "6173ddf33ed0wef368a094zq",
"collectionaID": "6173ddf33ed09368a094e68a",
"data": [
{
"userID": "123",
"visibility": false,
"response": false
},
{
"userID": "2345",
"visibility": true,
"response": true
}
]
},
{
"_id": "6173ddf33ed09368awef4e68g",
"collectionaID": "61wefdf33ed09368a094e6dc",
"data": [
{
"userID": "5678",
"visibility": false,
"response": false
},
{
"userID": "674",
"visibility": true,
"response": false
}
]
}
]
}
So What I need is documents from collection A which has response false in collection B
and document should be sorted by first the ones that have visibility false and then the ones that have visibility true
for eg. userID : 123 should get 3 documents
{
"_id": "6173ddf33ed09368a094e68a",
"title": "a"
},
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
whereas userID 2345 should get two
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
User 674 will receive 3 objects from collection A but second would be in the last as it has visibility true for that document
{
"_id": "6173ddf33ed09368a094e68a",
"title": "a"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
MongoDB Playground link : https://mongoplayground.net/p/3rLry0FPlw-
Really appreciate the help. Thanks
You can start from collectionA:
$lookup the collectionB for the record related to the user specified
filter out collectionB documents according to response
assign a helper sortrank field based on the visibility and whether collectionaID is a match
$sort according to sortrank
wrangle back to the raw collection A
db.collectiona.aggregate([
{
"$lookup": {
"from": "collectionb",
let: {
aid: "$_id"
},
"pipeline": [
{
$unwind: "$data"
},
{
$match: {
$expr: {
$and: [
{
$eq: [
"$data.userID",
"2345"
]
},
{
$eq: [
"$collectionaID",
"$$aid"
]
}
]
}
}
}
],
"as": "collB"
}
},
{
$match: {
"collB.data.response": {
$ne: true
}
}
},
{
"$unwind": {
path: "$collB",
preserveNullAndEmptyArrays: true
}
},
{
"$addFields": {
"sortrank": {
"$cond": {
"if": {
$eq: [
"$collB.data.visibility",
false
]
},
"then": 1,
"else": {
"$cond": {
"if": {
$eq: [
"$collB.collectionaID",
"$_id"
]
},
"then": 3,
"else": 2
}
}
}
}
}
},
{
$sort: {
sortrank: 1
}
},
{
$project: {
collB: false,
sortrank: false
}
}
])
Here is the Mongo playground for your reference.

How to count embedded array object elements in mongoDB

{
"orderNo": "123",
"bags": [{
"type": "small",
"products": [{
"id": "1",
"name": "ABC",
"returnable": true
}, {
"id": "2",
"name": "XYZ"
}
]
},{
"type": "big",
"products": [{
"id": "3",
"name": "PQR",
"returnable": true
}, {
"id": "4",
"name": "UVW"
}
]
}
]
}
I have orders collection where documents are in this format. I want to get a total count of products which has the returnable flag. e.g: for the above order the count should be 2. I am very new to MongoDB wanted to know how to write a query to find this out, I have tried few things but did not help:
this is what I tried but not worked:
db.orders.aggregate([
{ "$unwind": "$bags" },
{ "$unwind": "$bags.products" },
{ "$unwind": "$bags.products.returnable" },
{ "$group": {
"_id": "$bags.products.returnable",
"count": { "$sum": 1 }
}}
])
For inner array you can use $filter to check returnable flag and $size to get number of such items. For the outer one you can take advantage of $reduce to sum the values from inner arrays:
db.collection.aggregate([
{
$project: {
totalReturnable: {
$reduce: {
input: "$bags",
initialValue: 0,
in: {
$add: [
"$$value",
{
$size: {
$filter: {
input: "$$this.products",
as: "prod",
cond: {
$eq: [ "$$prod.returnable", true ]
}
}
}
]
}
}
}
}
}
}
])
Mongo Playground

Group by date in mongoDB while counting other fields

I've been using MongoDB for just a week and I have problems achieving this result: I want to group my documents by date while also keeping track of the number of entries that have a certain field set to a certain value.
So, my documents look like this:
{
"_id" : ObjectId("5f3f79fc266a891167ca8f65"),
"recipe" : "A",
"timestamp" : ISODate("2020-08-22T09:38:36.306Z")
}
where recipe is either "A", "B" or "C". Right now I'm grouping the documents by date using this pymongo query:
mongo.db.aggregate(
# Pipeline
[
# Stage 1
{
"$project": {
"createdAt": {
"$dateToString": {
"format": "%Y-%m-%d",
"date": "$timestamp"
}
},
"progressivo": 1,
"temperatura_fusione": 1
}
},
# Stage 2
{
"$group": {
"_id": {
"createdAt": "$createdAt"
},
"products": {
"$sum": 1
}
}
},
# Stage 3
{
"$project": {
"label": "$_id.createdAt",
"value": "$products",
"_id": 0
}
}])
Which gives me results like this:
[{"label": "2020-08-22", "value": 1}, {"label": "2020-08-15", "value": 2}, {"label": "2020-08-11", "value": 1}, {"label": "2020-08-21", "value": 5}]
What I'd like to have is also the counting of how many times each recipe appears on every date. So, if for example on August 21 I have 2 entries with the "A" recipe, 3 with the "B" recipe and 0 with the "C" recipe, the desired output would be
{"label": "2020-08-21", "value": 5, "A": 2, "B":3, "C":0}
Do you have any tips?
Thank you!
You can do like following, what have you done is excellent. After that,
In second grouping, We just get total value and value of each recipe.
$map is used to go through/modify each objects
$arrayToObject is used to covert the array what we have done via map (key : value pair) to object
$ifNull is used for, sometimes your data might not have "A" or "B" or "C". But you need the value should be 0 if there is no name as expected output.
Here is the code
[
{
"$project": {
"createdAt": {
"$dateToString": {
"format": "%Y-%m-%d",
"date": "$timestamp"
}
},
recipe: 1,
"progressivo": 1,
"temperatura_fusione": 1
}
},
{
"$group": {
"_id": {
"createdAt": "$createdAt",
"recipeName": "$recipe",
},
"products": {
$sum: 1
}
}
},
{
"$group": {
"_id": "$_id.createdAt",
value: {
$sum: "$products"
},
recipes: {
$push: {
name: "$_id.recipeName",
val: "$products"
}
}
}
},
{
$project: {
"content": {
"$arrayToObject": {
"$map": {
"input": "$recipes",
"as": "el",
"in": {
"k": "$$el.name",
"v": "$$el.val"
}
}
}
},
value: 1
}
},
{
$project: {
_id: 1,
value: 1,
A: {
$ifNull: [
"$content.A",
0
]
},
B: {
$ifNull: [
"$content.B",
0
]
},
C: {
$ifNull: [
"$content.C",
0
]
}
}
}
]
Working Mongo playground

MongoDb aggregation with arrays inside an array possible

I am struggling to find some examples of using the mongo aggregation framework to process documents which has an array of items where each item also has an array of other obejects (array containing an array)
In the example document below what I would really like is an example that sums the itemValue in the results array of all cases in the document and accross the collection where the result.decision was 'accepted'and group by the document locationCode
However, even an example that found all documents where the result.decision was 'accepted' to show or that summmed the itemValue for the same would help
Many thanks
{
"_id": "333212",
"data": {
"locationCode": "UK-555-5566",
"mode": "retail",
"caseHandler": "A N Other",
"cases": [{
"caseId": "CSE525666",
"items": [{
"id": "333212-CSE525666-1",
"type": "hardware",
"subType": "print cartridge",
"targetDate": "2020-06-15",
"itemDetail": {
"description": "acme print cartridge",
"quantity": 2,
"weight": "1.5"
},
"result": {
"decision": "rejected",
"decisionDate": "2019-02-02"
},
"isPriority": true
},
{
"id": "333212-CSE525666-2",
"type": "Stationery",
"subType": "other",
"targetDate": "2020-06-15",
"itemDetail": {
"description": "staples box",
"quantity": 3,
"weight": "1.66"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-03-03",
"itemValue": "23.01"
},
"isPriority": true
}
]
},
{
"caseId": "CSE885655",
"items": [{
"id": "333212-CSE885655-1",
"type": "marine goods",
"subType": "fish food",
"targetDate": "2020-06-04",
"itemDetail": {
"description": "fish bait",
"quantity": 5,
"weight": "0.65"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-03-02"
},
"isPriority": false
},
{
"id": "333212-CSE885655-4",
"type": "tobacco products",
"subType": "cigarettes",
"deadlineDate": "2020-06-15",
"itemDetail": {
"description": "rolling tobbaco",
"quantity": 42,
"weight": "2.25"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-02-02",
"itemValue": "48.15"
},
"isPriority": true
}
]
}
]
},
"state": "open"
}
You're probably looking for $unwind. It takes an array within a document and creates a separate document for each array member.
{ foos: [1, 2] } -> { foos: 1 }, { foos: 2}
With that you can create a flat document structure and match & group as normal.
db.collection.aggregate([
{
$unwind: "$data.cases"
},
{
$unwind: "$data.cases.items"
},
{
$match: {
"data.cases.items.result.decision": "accepted"
}
},
{
$group: {
_id: "$data.locationCode",
value: {
$sum: {
$toDecimal: "$data.cases.items.result.itemValue"
}
}
}
},
{
$project: {
_id: 0,
locationCode: "$_id",
value: "$value"
}
}
])
https://mongoplayground.net/p/Xr2WfFyPZS3
Alternative solution...
We group by data.locationCode and sum all items with this condition:
cases[*].items[*].result.decision" == "accepted"
db.collection.aggregate([
{
$group: {
_id: "$data.locationCode",
itemValue: {
$sum: {
$reduce: {
input: "$data.cases",
initialValue: 0,
in: {
$sum: {
$concatArrays: [
[ "$$value" ],
{
$map: {
input: {
$filter: {
input: "$$this.items",
as: "f",
cond: {
$eq: [ "$$f.result.decision", "accepted" ]
}
}
},
as: "item",
in: {
$toDouble: {
$ifNull: [ "$$item.result.itemValue", 0 ]
}
}
}
}
]
}
}
}
}
}
}
}
])
MongoPlayground

Projecting specific fields present inside an array, based on the value of some other field

Overview :
The documents, that I'm working upon, have two nested arrays in them - contentMetaData & text_content.
Within contentMetaData, we have the text_content and content_flag. Based on the value of the content_flag, I need to hide specific field within the text_content.
Requirement :
If the content_flag is true, text_content should have a single child - the text_note.
If the content_flag is false, text_content should have a single child - the text_description.
The structure and other details need to be preserved.
Documents SHOULD NOT be updated; the values need to be only hidden during projection.
Version Used : Mongo 2.6
Sample Document :
{
"_id": ObjectId("56f8dd19e4b0365115927b0f"),
"contentId": "cbc91805-2faa-4eff-8f84-02547173c152",
"contentMetaData": [
{
"_id": "1574b58f-b7fa-4cd5-b34f-98beeb657c97",
"name": "text_content",
"attributes": [],
"children": [
{
"_id": "97340ecf-fdbd-41e5-a6b2-01cc542f16ee",
"name": "text_note",
"value": "abc",
"type": "java.lang.String",
"attributes": [],
"children": [],
"noOfChildren": 0,
"positionIndex": 1
},
{
"_id": "19c5a3fb-54a2-4368-a89d-ea1d2554402d",
"name": "text_description",
"value": "def",
"type": "java.lang.String",
"attributes": [],
"children": [],
"noOfChildren": 0,
"positionIndex": 2
}
],
"noOfChildren": 2,
"positionIndex": 1
},
{
"_id": "4e8ef7c9-cffd-4b36-9109-89b263dff3c8",
"name": "content_flag",
"value": "true",
"type": "java.lang.String",
"attributes": [],
"children": [],
"noOfChildren": 0,
"positionIndex": 2
}
]
}
Sample Output :
{
"_id": ObjectId("56f8dd19e4b0365115927b0f"),
"contentId": "cbc91805-2faa-4eff-8f84-02547173c152",
"contentMetaData": [
{
"_id": "1574b58f-b7fa-4cd5-b34f-98beeb657c97",
"name": "text_content",
"attributes": [],
"children": [
{
"_id": "97340ecf-fdbd-41e5-a6b2-01cc542f16ee",
"name": "text_note",
"value": "abc",
"type": "java.lang.String",
"attributes": [],
"children": [],
"noOfChildren": 0,
"positionIndex": 1
}
],
"noOfChildren": 2,
"positionIndex": 1
},
{
"_id": "4e8ef7c9-cffd-4b36-9109-89b263dff3c8",
"name": "content_flag",
"value": "true",
"type": "java.lang.String",
"attributes": [],
"children": [],
"noOfChildren": 0,
"positionIndex": 2
}
]
}
I attempted using $map but it didn't work. I tried using $unwind, but was unable to $push the data back, in the desired format.
Sample Mongo Code :
db.content.aggregate([
{
$project: {
_id: 1,
contentId: 1,
contentMetaData: 1
tempMetaData: "$contentMetaData"
}
},
{
$unwind: "$contentMetaData"
},
{
$match: {
"contentMetaData.name": "content_flag"
}
},
{
$project: {
_id: 1,
contentId: 1,
contentMetaData: "$tempMetaData",
content_flag_value: "$contentMetaData.value"
}
},
{
$project: {
_id: 1,
contentId: 1,
contentMetaData: 1,
tempMetaData: "$contentMetaData",
content_flag_value: 1
}
},
{
$unwind: "$contentMetaData"
},
{
$match: {
"contentMetaData.name": "text_content"
}
},
{
$project: {
_id: 1,
contentId: 1,
contentMetaData: 1,
tempMetaData: "$contentMetaData",
content_flag_value: 1,
text_content : "$contentMetaData.children",
temp_text_content: "$text_content"
}
},
{
$unwind: "$text_content"
},
{
$group:{
_id:"$_id",
contentId:{$first:"$contentId"},
text_content:
{$max:
{$cond:
[
{$eq: ["$content_flag_value", "true"]},
{$cond:
[{$or:[
{$eq: ["$text_content.name","wk_link_url"]},
{$eq: ["$text_content.name","wk_link_description"]}
]},
"$text_content",
null]
},
null
]
}
},
contentMetaData:{$first:"$contentMetaData"}
}
},
{
$group:{
_id:"$_id",
contentId:{$first:"$contentId"},
contentMetaData:{$push:{"text_content":"$text_content"}}
}
},
{
$project: {
_id: 0,
contentId: 1,
contentMetaData: 1
}
}]).pretty()
I'm new to Mongo. Can somebody help me out with this?
You can try the below aggregation.
$map in combination with $setDifference to extract text_content and content_flag array.
$unwind to content_flag document.
$map to keep the current values in text_content and $map in combination with $setDifference to filter the children on the criteria.
$setUnion to join back the text_content and content_flag array into contentMetaData
db.collection.aggregate({
$project: {
_id: 1,
contentId: 1,
text_content: {
"$setDifference": [{
"$map": {
"input": "$contentMetaData",
"as": "text",
"in": {
"$cond": [{
$eq: ['$$text.name', "text_content"]
},
"$$text",
false
]
}
}
},
[false]
]
},
content_flag: {
"$setDifference": [{
"$map": {
"input": "$contentMetaData",
"as": "content",
"in": {
"$cond": [{
$eq: ['$$content.name', "content_flag"]
},
"$$content",
false
]
}
}
},
[false]
]
}
}
}, {
$unwind: "$content_flag"
}, {
$project: {
"_id": 1,
contentId: 1,
"contentMetaData": {
$setUnion: [{
$map: {
input: "$text_content",
as: "text",
in: {
"_id": "$$text._id",
"name": "$$text.name",
"attributes": "$$text.attributes",
"noOfChildren": "$$text.noOfChildren",
"positionIndex": "$$text.positionIndex",
"children": {
"$setDifference": [{
"$map": {
"input": "$$text.children",
"as": "child",
"in": {
"$cond": [{
"$cond": [{
$eq: ["$content_flag.value", "true"]
}, {
$eq: ["$$child.name", "text_note"]
}, {
$eq: ["$$child.name", "text_description"]
}]
},
"$$child",
false
]
}
}
},
[false]
]
}
}
}
},
["$content_flag"]
]
}
}
})
Update:
$map in combination with $setDifference to extract content_flag array.
$unwind to content_flag document.
$redact to go through a document level at a time and look for name field recursively and perform $$DESCEND and $$PRUNE on the criteria.
$project to format the final response.
db.collection.aggregate({
$project: {
_id: 1,
contentId: 1,
contentMetaData: 1,
content_flag: {
"$setDifference": [{
"$map": {
"input": "$contentMetaData",
"as": "content",
"in": {
"$cond": [{
$eq: ['$$content.name', "content_flag"]
},
"$$content",
false
]
}
}
},
[false]
]
}
}
}, {
$unwind: "$content_flag"
}, {
$redact: {
$cond: [{
$or: [{
$eq: ["$name", "text_content"]
}, {
$not: "$name"
}, {
$eq: ["$name", "content_flag"]
}, {
$and: [{
$eq: ["$name", "text_note"]
}, {
$eq: ["$$ROOT.content_flag.value", "true"]
}]
}, {
$and: [{
$eq: ["$name", "text_description"]
}, {
$eq: ["$$ROOT.content_flag.value", "false"]
}]
}]
},
"$$DESCEND",
"$$PRUNE"
]
}
}, {
$project: {
_id: 1,
contentId: 1,
contentMetaData: 1
}
});