How to get most repeated string array in Pymongo? - python-3.x

How can I get most repeated value for gender and age respectively?
My data:
[{ "_id": ObjectId("5dff27c0ac2d1547d87a1fe7"), "time": "2019-12-20 21:09:53",
"object": [{"Id": 1,"gender": "female","age": "0-10"},
{"Id": 2,"gender": "female","age": "20-30"}]
},
{ "_id": ObjectId("5dff27c0ac2d1547d87a1fe8"), "time": "2019-12-20 21:09:53",
"object": [{"Id": 1,"gender": "male","age": "0-10"},
{"Id": 2,"gender": "female","age": "30-40"}]
} ,
{ "_id": ObjectId("5dff27c0ac2d1547d87a1fe9"), "time": "2019-12-20 21:09:53",
"object": [{"Id": 1,"gender": "male","age": "10-15"},
{"Id": 2,"gender": "female","age": "30-40"},
{"Id": 3,"gender": "male","age": "0-10"}]
},
{ "_id": ObjectId("5dff27c0ac2d1547d87a1fea"), "time": "2019-12-20 21:09:53",
"object": [{"Id": 2,"gender": "male","age": "40-50"},
{"Id": 3,"gender": "male","age": "0-10"},
{"Id": 4,"gender": "male","age": "0-10"}]
}]
I have written below query,
mongo.db.xyz.aggregate([
{ "$unwind" : "$object"},
{"$group" : {"_id" : "$object.Id","_gen":{"$push":"$object.gender"},"_age":{"$push":"$object.age"}}},
{ "$project": { "_id" : "$_id", "gender":"$_gen","age":"$_age"}}
])
Below is the result I am getting,
[{"_id": 3,"age": ["0-10","0-10"],"gender": ["male","male"]},
{"_id": 2,"age": ["20-30","30-40","30-40","40-50"],"gender": ["female","female","female","male"]},
{"_id": 4,"age": ["0-10"],"gender": ["male"]},
{"_id": 1,"age": ["0-10","0-10","10-15"],"gender": ["female","male","male"]}
]
But I want the output to be ,
[{"_id": 3,"age": "0-10","gender": "male"},
{"_id": 2,"age": "30-40","gender": "female"},
{"_id": 4,"age": "0-10","gender": "male"},
{"_id": 1,"age": "0-10","gender": "male"}
]

Thinking about this problem I realized it was not so simple to get the mode of some fields independently in an array of objects with just one db query. To solve that I created a query to do it generically, based on your sample data.
db.collection.aggregate([
{
$unwind: {
path: "$arr"
}
},
{
$project: {
arr: {
$objectToArray: "$arr"
}
}
},
{
$unwind: {
path: "$arr"
}
},
{
$group: {
_id: {
_id: "$_id",
k: "$arr.k",
v: "$arr.v"
},
count: {
$sum: 1
}
}
},
{
$sort: {
count: -1
}
},
{
$group: {
_id: {
_id: "$_id._id",
k: "$_id.k"
},
v: {
$first: "$_id.v"
},
count: {
$first: "$count"
}
}
},
{
$group: {
_id: {
_id: "$_id._id"
},
arr: {
$push: {
k: "$_id.k",
v: {
mode: "$v",
count: "$count"
}
}
}
}
},
{
$project: {
arr: {
$arrayToObject: "$arr"
}
}
}
])
The secret to do that is to use the $objectToArray and the $arrayToObject operations, along with the $unwind and the correct $group stages. If you need a more detailed response on any stage please ask in the comments.
The output of the sample data is:
[
{
"_id": {
"_id": ObjectId("5dff27c0ac2d1547d87a1fea")
},
"arr": {
"Id": {
"count": 1,
"mode": 3
},
"age": {
"count": 2,
"mode": "0-10"
},
"gender": {
"count": 3,
"mode": "male"
}
}
},
{
"_id": {
"_id": ObjectId("5dff27c0ac2d1547d87a1fe7")
},
"arr": {
"Id": {
"count": 1,
"mode": 2
},
"age": {
"count": 1,
"mode": "0-10"
},
"gender": {
"count": 2,
"mode": "female"
}
}
},
{
"_id": {
"_id": ObjectId("5dff27c0ac2d1547d87a1fe9")
},
"arr": {
"Id": {
"count": 1,
"mode": 2
},
"age": {
"count": 1,
"mode": "30-40"
},
"gender": {
"count": 2,
"mode": "male"
}
}
},
{
"_id": {
"_id": ObjectId("5dff27c0ac2d1547d87a1fe8")
},
"arr": {
"Id": {
"count": 1,
"mode": 2
},
"age": {
"count": 1,
"mode": "30-40"
},
"gender": {
"count": 1,
"mode": "female"
}
}
}
]
After running it for a collection that has an array of objects named "arr" (edit the query if in your collection it has a different name) it will return the mode value and the number of occurrences of that value for each field. Objects that has that field unset will be not considered, but the ones with "null" will.

Related

Fill missing records in mongo aggregate

I have a collection request
{
_Id: '5b8c0f3204a10228b00a1745,
createdAt: '2018-09-07T17:18:40.759Z',
type: "demo" , //["demo","free-try","download",...]
}
And I have a query for fetching the daily number for a specific type.
Query
Model.aggregate([
{
$match: { $expr: { $and: filters } },
},
{
$project: {
day: { $substr: ["$createdAt", 0, 10] },
type: 1,
createdAt: 1,
},
},
{
$group: {
_id: {
day: "$day",
type: "$type",
},
total: { $sum: 1 },
},
},
{
$sort: { _id: 1 },
},
{
$project: {
_id: "$_id.day",
date: "$_id.day",
type: "$_id.type",
total: 1,
},
}
])
So I get these results :
[
{
"total": 1,
"_id": "2021-01-06",
"date": "2021-01-06",
"type": "print"
},
{
"total": 1,
"_id": "2021-01-13",
"date": "2021-01-13",
"type": "download"
},
{
"total": 1,
"_id": "2021-03-09",
"date": "2021-03-09",
"type": "test"
},
{
"total": 2,
"_id": "2021-03-29",
"date": "2021-03-29",
"type": "demo"
},
{
"total": 1,
"_id": "2021-04-20",
"date": "2021-04-20",
"type": "test"
},
{
"total": 1,
"_id": "2021-04-21",
"date": "2021-04-21",
"type": "download"
},
{
"total": 1,
"_id": "2021-04-21",
"date": "2021-04-21",
"type": "renew"
},
{
"total": 1,
"_id": "2021-04-22",
"date": "2021-04-22",
"type": "print"
},
{
"total": 2,
"_id": "2021-04-26",
"date": "2021-04-26",
"type": "renew"
},
{
"total": 1,
"_id": "2021-05-03",
"date": "2021-05-03",
"type": "test"
},
{
"total": 1,
"_id": "2021-05-05",
"date": "2021-05-05",
"type": "print"
},
{
"total": 1,
"_id": "2021-05-05",
"date": "2021-05-05",
"type": "test"
},
{
"total": 2,
"_id": "2021-05-31",
"date": "2021-05-31",
"type": "demo"
},
{
"total": 1,
"_id": "2021-06-03",
"date": "2021-06-03",
"type": "renew"
}
]
up to here, everything is fine, but when I need to fill the missing record, so for example if in '2021-06-03' I don't have any request of type "demo" I need to insert this object with a total of 0
{
"total": 0,
"_id": "2021-05-31",
"date": "2021-05-31",
"type": "demo"
}
so I add this pipeline based on a solution proposed in here
Model.aggregate([
{
$match: { $expr: { $and: filters } },
},
{
$project: {
day: { $substr: ["$createdAt", 0, 10] },
type: 1,
createdAt: 1,
},
},
{
$group: {
_id: {
day: "$day",
type: "$type",
},
total: { $sum: 1 },
},
},
{
$sort: { _id: 1 },
},
{
$project: {
_id: "$_id.day",
date: "$_id.day",
type: "$_id.type",
total: 1,
},
},
{
$group: {
_id: null,
stats: { $push: "$$ROOT" },
},
},
{
$project: {
stats: {
$map: {
input: ["2018-09-01", "2018-09-02", "2018-09-03", "2018-09-04", "2018-09-05", "2018-09-06"],
as: "date",
in: {
$let: {
vars: { dateIndex: { $indexOfArray: ["$stats._id", "$$date"] } },
in: {
$cond: {
if: { $ne: ["$$dateIndex", -1] },
then: { $arrayElemAt: ["$stats", "$$dateIndex"] },
else: { _id: "$$date", date: "$$date", total: 0,type: "download" },
},
},
},
},
},
},
},
},
{
$unwind: "$stats",
},
{
$replaceRoot: {
newRoot: "$stats",
},
},
])
but this solution adds only a single object by missing day, and I need an object per type, so any solution would be appreciated
You can simply do it with $facet
$facet helps to categorize the incoming data. So I get two arrays. One is match dates and another one is non match dates. In the match dates we need to add the condition
$concatArrays to join multiple arrays into one
$unwind to deconstruct the array
$replaceRoot to make it to root
Here is the code
db.collection.aggregate([
{
"$facet": {
"matchDate": [
{
$match: {
date: { $in: [ "2021-01-13","2021-04-21" ] }
}
},
{
$addFields: {
total: { $cond: [{ $eq: [ "$type", "demo" ]}, 0, "$total" ] }
}
}
],
"nonMatchDate": [
{
$match: {
date: { $nin: [ "2021-01-13", "2021-04-21" ] }
}
}
]
}
},
{
$project: {
combined: {
"$concatArrays": [ "$matchDate", "$nonMatchDate" ]
}
}
},
{ "$unwind": "$combined" },
{ "$replaceRoot": { "newRoot": "$combined" }}
])
Working Mongo playground

Use Mongoose aggregate to fetch object inside of an array

Here is my MongoDB schema:
{
"_id": "603f23ff6c1d862e5ced9e35",
"reviews": [
{
"like": 0,
"dislike": 0,
"_id": "603f23ff6c1d862e5ced9e34",
"userID": "5fd864abb53d452e0cbb5ef0",
"comment": "Not so good",
},
{
"like": 0,
"dislike": 0,
"_id": "603f242a6c1d862e5ced9e36",
"userID": "5fd864abb53d452e0cbb5ef0",
"comment": "Not so good",
}
]
productID:"hdy6nch99dndn"
}
I want to use aggregate to get the review object of a particular id. I tried but not with any success.
Here is my code:
ProductReview.aggregate([
{ $match: { productID: productID } }
])
$match
$unwind
db.collection.aggregate([
{
$match: {
productID: 1
}
},
{
$unwind: "$reviews"
},
{
$match: {
"reviews._id": 2
}
}
])
Output:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"productID": 1,
"reviews": {
"_id": 2,
"comment": "second comment",
"dislikes": [
{
"userID": 3
},
{
"userID": 4
}
],
"likes": [
{
"userID": 1
},
{
"userID": 2
}
]
}
}
]
Mongo Playground: https://mongoplayground.net/p/qfWS1rCuMfc

How to populate an ObjectID in group command in mongoDB?

I have collection named "report" like this:
{
"_id" : ObjectId("5fc51722d6827f3bfd24e3b0"),
"is_deleted" : false,
"reporter" : ObjectId("5fb7b85f516b9709af5c7bc2"),
"violator" : ObjectId("5fb8a07e9cd2840f5f6bac5a"),
"reportNote" : "vi pham",
"status" : 0,
"createdAt" : ISODate("2020-11-30T16:00:34.013Z"),
"updatedAt" : ISODate("2020-11-30T16:00:34.013Z"),
"__v" : 0
}
With "reporter" and "violator" is ObjectID that reference from "User" collection
Now I want to find a list of violator and re-oder it from larger to small, so I do like this.
db.report.aggregate([
{ $group: { _id: "$violator", count: { $sum: 1 } } },
{ $sort: { count: -1 } }
])
And I have result as below.
{
"data": [
{
"_id": "5fb8a07e9cd2840f5f6bac5a",
"count": 10
},
{
"_id": "5fbcbe855e26df3af08ffcee",
"count": 7
},
{
"_id": "5fbcb990cb35042db064b2b0",
"count": 6
}
],
"total": 23,
"message": ""
}
My expected result is
{
"data": [
{
"_id": "5fb8a07e9cd2840f5f6bac5a",
"name": "David",
"email": "david#gmail.com",
"count": 10
},
{
"_id": "5fbcbe855e26df3af08ffcee",
"name": "Vincent",
"email": "Vincent#gmail.com",
"count": 7
},
{
"_id": "5fbcb990cb35042db064b2b0",
"name": "robert",
"email": "robert#gmail.com",
"count": 6
}
],
"total": 23,
"message": ""
}
I did follow turivishal recommend.
db.report.aggregate([
{ $group: { _id: "$violator", count: { $sum: 1 } } },
{ $sort: { count: -1 } },
{
$lookup:
{
from: "users",
localField: "violator",
foreignField: "_id",
as: "ViolatorDetail"
}
}
])
But the result of ViolatorDetail (User) is empty.
{
"data": [
{
"_id": {
"violator": "5fb8a07e9cd2840f5f6bac5a",
"status": 0,
"reportNote": "vi pham"
},
"count": 10,
"ViolatorDetail": []
},
{
"_id": {
"violator": "5fbcbe855e26df3af08ffcee",
"status": 0,
"reportNote": "vi pham"
},
"count": 7,
"ViolatorDetail": []
},
{
"_id": {
"violator": "5fbcb990cb35042db064b2b0",
"status": 0,
"reportNote": "vi pham"
},
"count": 6,
"ViolatorDetail": []
}
],
"total": 23,
"message": ""
}

MongoDB aggregation : Group by Category and sum up the amount

I have the following structure in my collection (you don't have to mind the status) :
{
"_id": {
"$oid": "5e6355e71b14ee00175698cb"
},
"finance": {
"expenditure": [
{
"status": true,
"_id": { "$oid": "5e63562d1b14ee00175698df" },
"amount": { "$numberInt": "100" },
"category": "Sport"
},
{
"status": true,
"_id": { "$oid": "5e6356491b14ee00175698e0" },
"amount": { "$numberInt": "200" },
"category": "Sport"
},
{
"status": true,
"_id": { "$oid": "5e63565b1b14ee00175698e1" },
"amount": { "$numberInt": "50" },
"category": "Outdoor"
},
{
"status": true,
"_id": { "$oid": "5e63566d1b14ee00175698e2" },
"amount": { "$numberInt": "400" },
"category": "Outdoor"
}
]
}
}
My previos command was this:
User.aggregate([
{ $match: {_id: req.user._id} },
{ $unwind: '$finance.expenditure' },
{ $match: {'finance.expenditure.status': true} },
{ $sort: {'finance.expenditure.currentdate': -1} },
{
$group: {
_id: '$_id',
expenditure: { $push: '$finance.expenditure' }
}
}
])
With this I just get every single expenditure back.
But now I want to group the expenditures by their category and sum up the amount of every single expenditure for their group.
So it should look like this:
{ "amount": 300 }, "category": "Sport" },
{ "amount": 450 }, "category": "Outdoor" }
Thanks for your help
Instead of grouping on _id field group on category field & sum amount field:
db.collection.aggregate([
{ $match: {_id: req.user._id}},
{
$unwind: "$finance.expenditure"
},
{
$match: {
"finance.expenditure.status": true
}
},
{
$sort: {
"finance.expenditure.currentdate": -1
}
},
{
$group: {
_id: "$finance.expenditure.category",
amount: {
$sum: "$finance.expenditure.amount"
}
}
},
{
$project: {
_id: 0,
category: "$_id",
amount: 1
}
}
])
Test : MongoDB-Playground

MongoDB $lookup on one document's array of object

I have searched online but could not find any match my case. Here is the situation.
I am using aggregate to combine one collection and one document which is from another collection together
restaurants.aggregate([
{
$match: {
_id: {
$in: idList
}
}
},
{
$lookup: {
from: "tags",
localField: "details.restaurantType",
foreignField: "details.restaurantType._id",
as: "types"
}
},
{
$project: {
restaurantName: "$details.restaurantName",
restaurantType: "$details.restaurantType",
type: {
$filter: {
input: "$types",
as: "type",
cond: {
$eq: ["$$type._id", "$details.restaurantType"]
}
}
},
currency: "$details.currency",
costPerPax: "$details.costPerPax"
}
}
]);
current result
The 'type' field in my current result is [], I need a matched value instead
[
{
"id": "5c20c7a0036dda80a8baabcc",
"restaurantName": "Villagio Restaurant Sutera Mall",
"type": [],
"currency": "RM",
"costPerPax": 22,
},
{
"id": "5c20ceb07715216d3c217b7a",
"restaurantName": "Thai Food Thai Now Sutera Mall",
"type": [],
"currency": "RM",
"costPerPax": 16,
}
]
expected result
I need the 'type' fields has match tag name from another collection like this
[
{
"id": "5c20c7a0036dda80a8baabcc",
"restaurantName": "Villagio Restaurant Sutera Mall",
"type": "Western",
"currency": "RM",
"costPerPax": 22,
},
{
"id": "5c20ceb07715216d3c217b7a",
"restaurantName": "Thai Food Thai Now Sutera Mall",
"type": "Thai",
"currency": "RM",
"costPerPax": 16,
}
]
Extra Information
two document from restaurants collection
{
"details": {
"restaurantName": "Villagio Restaurant Sutera Mall",
"restaurantType": "5c01fb57497a896d50f498a8"
},
"_id": "5c20c7a0036dda80a8baabcc",
"status": "OP",
"__v": 0
},
{
"details": {
"restaurantName": "Kingshahi Japanese Shop",
"restaurantType": "5c01fb57497a896d50f49879"
},
"_id": "5c20cb4fb7e75180480690c2",
"status": "OP",
"__v": 0
}
One document from tag collection
{
"_id": "5c01fb57497a896d50f49876",
"details": {
"restaurantTypeId": "5c01fb57497a896d50f49877",
"restaurantTypes": [
{
"_id": "5c01fb57497a896d50f49879",
"name": "Asian",
"counter": 1
},
{
"_id": "5c01fb57497a896d50f4987a",
"name": "Bakery",
"counter": 0
}
]
}
}
You can use below optimised aggregation pipeline
db.restaurants.aggregate([
{ "$lookup": {
"from": "tags",
"let": { "restaurantType": "$details.restaurantType" },
"pipeline": [
{ "$match": {
"$expr": { "$in": ["$$restaurantType", "$details.restaurantTypes._id"] }
}},
{ "$unwind": "$details.restaurantTypes" },
{ "$match": {
"$expr": { "$eq": ["$details.restaurantTypes._id", "$$restaurantType"] }
}}
],
"as": "types"
}},
{ "$project": {
"restaurantName": "$details.restaurantName",
"restaurantType": "$details.restaurantType",
"type": { "$arrayElemAt": ["$types.details.restaurantTypes.name", 0] },
"currency": "$details.currency",
"costPerPax": "$details.costPerPax"
}}
])

Resources