Join on Related properties of two Arrays - mongodb

I have the following result. Below it would be possible to make a map joining the array each with its objectid.
{
"_id": ObjectId("597233b50e717e0585dbd94a"),
"createdAt": ISODate("2017-07-21T17:02:45.119+0000"),
"name": "cardoso",
"gender": "female",
"profile": [{
"profession": "master",
"_id": ObjectId("597233b50e717e0585dbd94b"),
"departament": ObjectId("597233b50e717e0585dbd94e")
},
{
"_id": ObjectId("59766719003e7d552fe40e8c"),
"profession": "master",
"departament": ObjectId("59766719003e7d552fe40e8b")
},
{
"_id": ObjectId("5976b8f99d8a4326c6bf1ae5"),
"profession": "master",
"departament": ObjectId("5974d8fe398e5b2fd433410f")
}
],
"Institution": {
"_id": ObjectId("597233b50e717e0585dbd94c"),
"cnpj": 64837134000144.0,
"deletedAt": false,
"departament": [{
"title": "cardoso",
"category": "Sub-17",
"_id": ObjectId("597233b50e717e0585dbd94e")
},
{
"sport": "Tênis",
"title": "novo",
"category": "Sub-12",
"_id": ObjectId("59766719003e7d552fe40e8b")
},
{
"_id": ObjectId("5974d8fe398e5b2fd433410f"),
"category": "Intercâmbio",
"title": "testeJJJ",
"sport": "natação"
}
]
}
}
I need to do the following result. I did not want to have to manipulate the result in the node.
{
"sport": "Tênis",
"profession": "master",
"title": "novo",
"category": "Sub-12",
"_id": ObjectId("59766719003e7d552fe40e8b")
}
I already tended to do something but the query ends up getting very big

The basic premise here is to "lookup" the content in the other array whilst processing via $map.
This is either done via $indexOfArray with MongoDB 3.4:
db.collection.aggregate([
{ "$addFields": {
"Institution": {
"departament": {
"$map": {
"input": "$Institution.departament",
"as": "d",
"in": {
"sport": "$$d.title",
"profession": {
"$arrayElemAt": [
"$profile.profession",
{ "$indexOfArray": [ "$profile.departament", "$$d._id" ] }
]
},
"title": "$$d.title",
"category": "$$d.category"
}
}
}
}
}}
])
In that first index we look for the "index position" from the "profile" array that matches the current value of _id on the specified field. Then extract the data at that index via $arrayElemAt.
Or using $filter and the $arrayElemAt "the other way around" with MongoDB 3.2:
db.collection.aggregate([
{ "$addFields": {
"Institution": {
"departament": {
"$map": {
"input": "$Institution.departament",
"as": "d",
"in": {
"sport": "$$d.title",
"profession": {
"$arrayElemAt": [
{ "$map": {
"input": {
"$filter": {
"input": "$profile",
"as": "p",
"cond": { "$eq": [ "$$p.departament", "$$d._id" ] }
}
},
"as": "p",
"in": "$$p.profession"
}},
0
]
},
"title": "$$d.title",
"category": "$$d.category"
}
}
}
}
}}
])
In which case the $filter reduces the array content in "profile" down to only matching elements, which should be just one. This is then extracted at the 0 index by $arrayElemAt.
Same result in either case:
{
"_id" : ObjectId("597233b50e717e0585dbd94a"),
"createdAt" : ISODate("2017-07-21T17:02:45.119Z"),
"name" : "cardoso",
"gender" : "female",
"profile" : [
{
"profession" : "master",
"_id" : ObjectId("597233b50e717e0585dbd94b"),
"departament" : ObjectId("597233b50e717e0585dbd94e")
},
{
"_id" : ObjectId("59766719003e7d552fe40e8c"),
"profession" : "master",
"departament" : ObjectId("59766719003e7d552fe40e8b")
},
{
"_id" : ObjectId("5976b8f99d8a4326c6bf1ae5"),
"profession" : "master",
"departament" : ObjectId("5974d8fe398e5b2fd433410f")
}
],
"Institution" : {
"_id" : ObjectId("597233b50e717e0585dbd94c"),
"cnpj" : 64837134000144.0,
"deletedAt" : false,
"departament" : [
{
"sport" : "cardoso",
"profession" : "master",
"title" : "cardoso",
"category" : "Sub-17"
},
{
"sport" : "novo",
"profession" : "master",
"title" : "novo",
"category" : "Sub-12"
},
{
"sport" : "testeJJJ",
"profession" : "master",
"title" : "testeJJJ",
"category" : "Intercâmbio"
}
]
}
}

Related

mongodb lookup on nested documents

I have documents like these :
Merchant:
{
"_id" : ObjectId("628728cf9f5c99d8dedbe759"),
"name" : "agriTales",
"information" : "Farmers social",
"categories" : [
ObjectId("6287220c52497922d6f4a121")
],
"website" : "www.agritales.com",
"active" : true
}
LuckyDraw:
{
"_id" : ObjectId("62873487e0e5eedc24748a55"),
"business" : ObjectId("628728cf9f5c99d8dedbe759"),
"title" : "May Lucky draw 2022",
"date_start" : ISODate("2022-04-26T00:00:00Z"),
"max_participants" : 5,
"active" : true,
"date_of_draw" : ISODate("2022-07-30T00:00:00Z"),
"prizes_winners" : [
{
"prize_name" : "1st Prize",
"coupon_voucher" : ObjectId("62872f809f5c99d8dedbe764")
},
{
"prize_name" : "2nd Prize",
"coupon_voucher" : ObjectId("62872f8d9f5c99d8dedbe765")
}
],
"result_declared" : true
}
CouponVoucher:
{
"_id" : ObjectId("62872f809f5c99d8dedbe764"),
"business" : ObjectId("628728cf9f5c99d8dedbe759"),
"type" : "voucher",
"title" : "8farmers Coupons 2001",
"minimum_order_value" : 0,
"voucher_face_value" : 2001,
"active" : true,
"status" : "NEW",
"date_expiry" : ISODate("2022-12-31T00:00:00Z"),
}
{
"_id" : ObjectId("62872f8d9f5c99d8dedbe765")
"type" : "voucher",
"title" : "8farmers Coupons 1501",
"minimum_order_value" : 0,
"voucher_face_value" : 1501,
"active" : true,
"status" : "NEW",
"date_expiry" : ISODate("2022-12-31T00:00:00Z")
}
class LuckyDraws(Document):
business = ReferenceField(Merchants, required=True)
branches = ListField(ReferenceField(MerchantBranches))
title = StringField(required=True)
date_start = DateTimeField(required=True) # the date when luck draw will be available to participate
max_participants = IntField() # max number of participants
active = BooleanField(default=True)
date_of_draw = DateTimeField(required=True) # the date when luck draw will be declared
filter_conditions = EmbeddedDocumentField(LuckyDrawFilterConditions)
participants = ListField(ReferenceField(Users), required=False)
prizes_winners = ListField(EmbeddedDocumentField(LuckyDrawPrizesWinners), required=True)
class LuckyDrawPrizesWinners(EmbeddedDocument):
prize_name = StringField(required=True)
coupon_voucher = ReferenceField(CouponVoucher, required=True)
winner = ReferenceField(Users, blank=True, null=True, default=None)
class CouponVoucher(Document):
business = ReferenceField(Merchants, required=True)
title = StringField(required=True, unique=True)
discount_type = StringField(required=False) # amount / percentage
coupon_discount = DecimalField(required=False)
max_count = IntField(required=False)
minimum_order_value = DecimalField(required=False)
I am looking for a result where I get details of the lucky draw and its corresponding prizes and couponvoucher details
used below pipeline
LuckyDraws.objects.aggregate([{
'$lookup':
{
"from": "merchants",
"localField": "business",
"foreignField": "_id",
"as": "business",
"pipeline": [
{"$project": {"name": 1, "information": 1, "categories": 1}}
]
}
},
{"$project":
{
"business": 1, "title": 1, "date_start": 1, "active": 1, "date_of_draw": 1,
"prizes_winners.prize_name": 1, "prizes_winners.coupon_voucher": 1,
"result_declared": 1, "vouchers": 1
}
},
{
'$lookup':
{
"from": "coupon_voucher",
"localField": "prizes_winners.coupon_voucher",
"foreignField": "_id",
"pipeline": [
{"$project": {"title": 1, "minimum_order_value": 1, "voucher_face_value": 1,
"active": 1, "date_expiry": 1}}
],
"as": "prizes_winners"
}
},
{
"$sort": {
"date_of_draw": -1
}
}
]
and getting below results but its missing prize_name under prizes_winners, please let me know what I am missing and how t oadd prize_name under prizes_winners.
the result i am getting :
[
{
"_id": {
"$oid": "62873487e0e5eedc24748a55"
},
"business": [
{
"_id": {
"$oid": "628728cf9f5c99d8dedbe759"
},
"name": "agriTales",
"information": "Farmers social",
"categories": [
{
"$oid": "6287220c52497922d6f4a121"
}
]
}
],
"title": "May Lucky draw 2022",
"date_start": {
"$date": "2022-04-26T00:00:00Z"
},
"active": true,
"date_of_draw": {
"$date": "2022-07-30T00:00:00Z"
},
"prizes_winners": [
{
"_id": {
"$oid": "62872f809f5c99d8dedbe764"
},
"title": "8farmers Coupons 2001",
"minimum_order_value": 0.0,
"voucher_face_value": 2001.0,
"active": true,
"date_expiry": {
"$date": "2022-12-31T00:00:00Z"
}
},
{
"_id": {
"$oid": "62872f8d9f5c99d8dedbe765"
},
"title": "8farmers Coupons 1501",
"minimum_order_value": 0.0,
"voucher_face_value": 1501.0,
"active": true,
"date_expiry": {
"$date": "2022-12-31T00:00:00Z"
}
}
],
"result_declared": true
}
]
I want the result should be :
[
{
"_id": {
"$oid": "62873487e0e5eedc24748a55"
},
"business": [
{
"_id": {
"$oid": "628728cf9f5c99d8dedbe759"
},
"name": "agriTales",
"information": "Farmers social",
"categories": [
{
"$oid": "6287220c52497922d6f4a121"
}
]
}
],
"title": "May Lucky draw 2022",
"date_start": {
"$date": "2022-04-26T00:00:00Z"
},
"active": true,
"date_of_draw": {
"$date": "2022-07-30T00:00:00Z"
},
"prizes_winners": [
{
**"prize_name":"1st prize",**
"_id": {
"$oid": "62872f809f5c99d8dedbe764"
},
"title": "8farmers Coupons 2001",
"minimum_order_value": 0.0,
"voucher_face_value": 2001.0,
"active": true,
"date_expiry": {
"$date": "2022-12-31T00:00:00Z"
}
},
{
**"prize_name":"2nd prize",**
"_id": {
"$oid": "62872f8d9f5c99d8dedbe765"
},
"title": "8farmers Coupons 1501",
"minimum_order_value": 0.0,
"voucher_face_value": 1501.0,
"active": true,
"date_expiry": {
"$date": "2022-12-31T00:00:00Z"
}
}
],
"result_declared": true
}
]
There are lots of ways, and possibly more efficient ways, to do this. Here's one way.
db.LuckyDraw.aggregate([
{
"$lookup": {
"from": "Merchant",
"localField": "business",
"foreignField": "_id",
"pipeline": [ { "$unset": [ "active", "website" ] } ],
"as": "business"
}
},
{
"$lookup": {
"from": "CouponVoucher",
"localField": "prizes_winners.coupon_voucher",
"foreignField": "_id",
"let": { "prizes_winners": "$prizes_winners" },
"pipeline": [
{
"$set": {
"prize_name": {
"$reduce": {
"input": "$$prizes_winners",
"initialValue": "",
"in": {
"$cond": [
{ "$eq": [ "$$this.coupon_voucher", "$_id" ] },
"$$this.prize_name",
"$$value"
]
}
}
}
}
}
],
"as": "prizes_winners"
}
}
])
Try it on mongoplayground.net.

I am having difficulty in querying the follwing nested document using pymongo

If these are the following nested documents
[
{
"_id": 5,
"name": "Wilburn Spiess",
"scores": [
{
"score": 44.87186330181261,
"type": "exam"
},
{
"score": 25.72395114668016,
"type": "quiz"
},
{
"score": 63.42288310628662,
"type": "homework"
}
]
},
{
"_id": 6,
"name": "Jenette Flanders",
"scores": [
{
"score": 37.32285459166097,
"type": "exam"
},
{
"score": 28.32634976913737,
"type": "quiz"
},
{
"score": 81.57115318686338,
"type": "homework"
}
]
},
{
"_id": 7,
"name": "Salena Olmos",
"scores": [
{
"score": 90.37826509157176,
"type": "exam"
},
{
"score": 42.48780666956811,
"type": "quiz"
},
{
"score": 96.52986171633331,
"type": "homework"
}
]
}
]
I need to access the score part 'type' = exam.
Can somebody help me with this?
If you're asking for a python program to access the score, you can print them out like:
collection = mongo_connection['db']['collection']
documents = collection.find({})
for doc in documents:
for score in doc['scores']:
if score['type'] == 'exam':
print(f'Score: {score["score"]}')
If you are trying to retrieve only the scores and ignore the rest, I'd do an $unwind on the scores, $match on the type, and then project the fields you want (or not).
db.test.aggregate([
{
$unwind: '$scores'
},
{
$match: {
'scores.type': 'exam'
}
},
{
$project: {
'name': '$name',
'score': '$scores.score'
}
}
])
This would output:
{
"_id" : 5,
"name" : "Wilburn Spiess",
"score" : 44.8718633018126
},
{
"_id" : 6,
"name" : "Jenette Flanders",
"score" : 37.322854591661
},
{
"_id" : 7,
"name" : "Salena Olmos",
"score" : 90.3782650915718
}

Find by biggest difference between elements on embedded object array

Given a list of products like this:
{
"_id" : ObjectId("5a594f8eff9da13c9d415a63"),
"productId" : "xxx",
"date" : "2018-09-13",
"prices" : [
{
"country" : "en",
"price" : 16.5,
"currency" : "EUR"
},
{
"country" : "es",
"price" : 17.78,
"currency" : "EUR"
},
{
"country" : "fr",
"price" : 18.08,
"currency" : "EUR"
},
{
"country" : "de",
"price" : 18.89,
"currency" : "EUR"
},
{
"country" : "it",
"price" : 27.49,
"currency" : "EUR"
}
]
}
Given a country code and a date, is there any way to find the products for that date and order by biggest different between price for the country?
Thank you very much in advance
Assuming that
you want the biggest difference between the given country and any other country and
there are no duplicate product ids (if there are, the latest product will be used, thanks to this line "$last": "$prices"),
try this:
db.collection.aggregate([
{
"$match": {
"date": "2018-09-13" // replace with date variable
}
},
{
"$group": {
"_id": "$productId",
"prices": {
"$last": "$prices"
}
}
},
{
"$addFields": {
"pricesObj": {
"$map": {
"input": "$prices",
"in": {
"k": "$$this.country",
"v": "$$this.price"
}
}
}
}
},
{
"$addFields": {
"pricesObj": {
"$arrayToObject": "$pricesObj"
}
}
},
{
"$addFields": {
"reference": "$pricesObj.es" // replace with country variable
}
},
{
"$addFields": {
"differences": {
"$map": {
"input": "$prices",
"in": {
"country": "$$this.country",
"difference": {
"$abs": {
"$subtract": [
"$$this.price",
"$reference"
]
}
}
}
}
}
}
},
{
"$addFields": {
"biggestDifference": {
"$reduce": {
"input": "$differences",
"initialValue": {
difference: 0
},
"in": {
"$cond": [
{
"$gt": [
"$$this.difference",
"$$value.difference"
]
},
"$$this",
"$$value"
]
}
}
}
}
},
{
"$project": {
"_id": 1,
"biggestDifference": "$biggestDifference.difference"
}
},
{
"$sort": {
"biggestDifference": -1
}
}
])
I'm sure it could be expressed more concisely, but it works: https://mongoplayground.net/p/y67jhhFBB9l
The output looks like:
[
{
"_id": "xxy",
"biggestDifference": 12295.109999999999
},
{
"_id": "xxx",
"biggestDifference": 98.72
}
]
for this input:
[
{
"productId": "xxx",
"date": "2018-09-13",
"prices": [
{
"country": "en",
"price": 116.5,
"currency": "EUR"
},
{
"country": "es",
"price": 17.78,
"currency": "EUR"
},
{
"country": "fr",
"price": 18.08,
"currency": "EUR"
},
{
"country": "de",
"price": 18.89,
"currency": "EUR"
},
{
"country": "it",
"price": 27.49,
"currency": "EUR"
}
]
},
{
"productId": "xxy",
"date": "2018-09-13",
"prices": [
{
"country": "en",
"price": 16.5,
"currency": "EUR"
},
{
"country": "es",
"price": 17.78,
"currency": "EUR"
},
{
"country": "fr",
"price": 18.08,
"currency": "EUR"
},
{
"country": "de",
"price": 12312.89,
"currency": "EUR"
},
{
"country": "it",
"price": 997.49,
"currency": "EUR"
}
]
}
]
Thank you #jaksz,
Finally, I'm using this approach that works like a charm (because the smaller price is always in the first position of the array):
db.productPrices.aggregate(
[
{
"$match": {
"date": "2018-09-13" // replace with date variable
}
},
{
"$group": {
"_id": "$productId",
"prices": {
"$last": "$prices"
}
}
},
{
"$addFields": {
"pricesObj": {
"$map": {
"input": "$prices",
"in": {
"k": "$$this.country",
"v": "$$this.price"
}
}
}
}
},
{
"$addFields": {
"pricesObj": {
"$arrayToObject": "$pricesObj"
}
}
},
{
"$addFields": {
"reference": "$pricesObj.es" // replace with country variable
}
},
{
"$addFields": {
"cheapest": {
"$arrayElemAt": ["$prices", 0]
}
}
},
{
"$addFields": {
"difference": {
"$abs": {
"$subtract": ["$reference", "$cheapest.price"]
}
}
}
},
{
"$project": {
"_id": 1,
"prices": "$prices",
"difference": "$difference"
}
},
{
"$sort": {
"difference": -1
}
}
]).pretty()

Display mongodb multiple group by query results in a nested format

Given the following dataset:
[
{
"account_id" : "1111"
"task_id" : "aaaa",
"workweek" : "20",
"hours": "18"
},
{
"account_id" : "1111"
"task_id" : "aaaa",
"workweek" : "20",
"hours": "12"
},
{
"account_id" : "1111"
"task_id" : "aaaa",
"workweek" : "21",
"hours": "10"
},
{
"account_id" : "1111"
"task_id" : "bbbb",
"workweek" : "21",
"hours": "5"
},
{
"account_id" : "2222"
"task_id" : "cccc",
"workweek" : "21",
"hours": "15"
}
]
I'd like to group the documents and have the results in the following format:
[
{
"account_id": "1111",
"tasks": [
{
"task_id": "aaaa",
"workweeks": [
{
"workweek": "20",
"total_hours": "30"
},
{
"workweek": "21",
"total_hours": "10"
}
]
},
{
"task_id": "bbbb",
"workweeks": [
{
"workweek": "21",
"total_hours": "5"
}
]
}
]
},
{
"account_id": "2222",
"tasks": [
{
"task_id": "cccc",
"workweeks": [
{
"workweek": "21",
"total_hours": "15"
}
]
},
]
}
]
My broken aggregation code is the following:
db.tasks.aggregate([
{"$group": {
"_id": {
"account_id": "$account_id",
"task_id": "$task_id",
"workweek": "$workweek",
},
"total_hours": {$sum: "$hours"}
}},
{"$group": {
"_id": "$_id.account_id",
"tasks": {
"$push": {
"task_id": "$_id.task_id",
"workweeks": {
"$push": {
"workweek": "$_id.workweek",
"total_hours": "$total_hours"
}
}
}
}
}}
]);
I receive this error:
"errmsg" : "exception: invalid operator '$push'",
And I assume it is because I can only $push once per $group block.
Any ideas on how to achieve the desired result?
Please try with one additional (middleware) "group-by" step:
db.tasks.aggregate([
{"$group": {
"_id": {
"account_id": "$account_id",
"task_id": "$task_id",
"workweek": "$workweek",
},
"total_hours": {$sum: "$hours"}
}},
{"$group": {
"_id": {
"$_id.account_id",
"$_id.task_id"
},
"workweeks": {
"$push": {
"workweek": "$_id.workweek",
"total_hours": "$total_hours"
}
}
}},
{"$group": {
"_id": "$_id.account_id",
"tasks": {
"$push": {
"task_id": "$_id.task_id",
"workweeks": "$workweeks"
}
}
}}
]);

MongoDB - Retrieve only the matching subdocument AND the root document

Edit:
Problem solved, see below.
I have the following document:
db.clients.insert({
_id: ObjectId("524d720d8d3ea014a52e95bb"),
company: "Example",
logins: [
{
"name": "John Smith",
"username": "test",
"password": "eF9wnBEys0OzL5vmR/OHGCaekHiw/Miy+XvbDdayxeo=",
"email": "a#a.com",
"last": null,
"roles": ["CONFIG"]
},
{
"name": "Guest",
"username": "guest",
"password": "K/gYODb7XPo0erySvL276DyPi4+stPPK4jM3pJ8aaVg=",
"email": "a#a.com",
"last": null,
"roles": []
}
]
});
And now, I want to authenticate my clients, using this document. But, I don't want to retrieve every sub logins, I want only the one which match.
That's why I'm using an aggregate:
db.clients.aggregate(
{
"$project": {
"login": "$logins",
"_id": 0
}
},
{
"$unwind": "$login"
},
{
"$group": {
"_id": "$login.username",
"login": {
"$first": "$login"
}
}
},
{
"$match": {
"login.username": "test",
"login.password": "eF9wnBEys0OzL5vmR/OHGCaekHiw/Miy+XvbDdayxeo=",
}
}
);
Which works fine, giving me:
{
"result" : [
{
"_id" : "test",
"login" : {
"name" : "John Smith",
"username" : "test",
"password" : "eF9wnBEys0OzL5vmR/OHGCaekHiw/Miy+XvbDdayxeo=",
"email" : "a#a.com",
"last" : null,
"roles" : [
"CONFIG"
]
}
}
],
"ok" : 1
}
But now, the tricky part is that I would like to retrieve also the root document fields. Such as _id and company for example.
But no matter what I try, I can't manage to do it. Do you have a solution? :)
Edit:
Ok, in fact it wasn't that hard. I'm sorry!
db.clients.aggregate(
{
"$project": {
"login": "$logins",
"_id": "$_id",
"company": "$company"
}
},
{
"$unwind": "$login"
},
{
"$group": {
"_id": "$login.username",
"login": {
"$first": "$login"
},
"clientId": {
"$first": "$_id"
},
"company": {
"$first": "$company"
},
}
},
{
"$match": {
"login.username": "test",
"login.password": "eF9wnBEys0OzL5vmR/OHGCaekHiw/Miy+XvbDdayxeo=",
}
}
);
You can do this with a find and the $ positional projection operator as well:
db.clients.find({
"logins.username": "test",
"logins.password": "eF9wnBEys0OzL5vmR/OHGCaekHiw/Miy+XvbDdayxeo=",
}, {
"logins.$": 1,
"company": 1
})
The $ in the projection contains the index of the logins array element that was matched in the query.
Output:
{
"_id": ObjectId("524d720d8d3ea014a52e95bb"),
"company": "Example",
"logins": [
{
"name": "John Smith",
"username": "test",
"password": "eF9wnBEys0OzL5vmR/OHGCaekHiw/Miy+XvbDdayxeo=",
"email": "a#a.com",
"last": null,
"roles": [
"CONFIG"
]
}
]
}
A bit shorter variant:
db.clients.aggregate(
{$match:{"logins.username":"test"}},
{$unwind:"$logins"},
{$match:{"logins.username":"test","logins.password":"eF9wnBEys0OzL5vmR/OHGCaekHiw/Miy+XvbDdayxeo="}}
)
Output is:
{
"result" : [
{
"_id" : ObjectId("524d720d8d3ea014a52e95bb"),
"company" : "Example",
"logins" : {
"name" : "John Smith",
"username" : "test",
"password" : "eF9wnBEys0OzL5vmR/OHGCaekHiw/Miy+XvbDdayxeo=",
"email" : "a#a.com",
"last" : null,
"roles" : [
"CONFIG"
]
}
}
],
"ok" : 1
}