mongodb aggregation with array and lookup - mongodb

I have a MongoDB collection with documents in the following format
collection name called post
{
"_id" : ObjectId("5c88b225fac24431d947abad"),
"user_id" : "5c87ad6c623f1e2bd4d041d0",
"post_likes" : [
{
"post_user_id" : "5c87ad6c623f1e2bd4d041d0",
"like_status" : true
},
{
"post_user_id" : "5c88b42b71611926c055508b",
"like_status" : true
}
],
"post_comments" : [
{
"comment_user_id" : "5c87ad6c623f1e2bd4d041d0",
"comment_like" : "",
"comment_description" : ""
},
{
"comment_user_id" : "5c88b42b71611926c055508b",
"comment_like" : "",
"comment_description" : "nice post"
}
]
}
i have another collection name called user_ptofile_info
{
"_id" : ObjectId("5c923682c088564cf01056cb"),
"user_id" : "5c87ad6c623f1e2bd4d041d0",
"image_url" : "image/url",
"user_name":"xxxxxxxx",
"created_at" : "",
"updated_at" : ""
}
requested output like
note:the post_user_id from user_ptofile_info and user_id from post are lookup and i need total count for post_likes and post comments also
{
"_id" : ObjectId("5c88b225fac24431d947abad"),
"user_id" : "5c87ad6c623f1e2bd4d041d0",
"post_likes" : [
{
"post_user_id" : "5c87ad6c623f1e2bd4d041d0",
"like_status" : true,
"image_url" : "image/url",
"user_name":"xxxxxxxx",
},
{
"post_user_id" : "5c88b42b71611926c055508b",
"like_status" : true,
"image_url" : "image/url",
"user_name":"xxxxxxxx",
}
],
"post_comments" : [
{
"comment_user_id" : "5c87ad6c623f1e2bd4d041d0",
"comment_like" : "",
"comment_description" : ""
},
{
"comment_user_id" : "5c88b42b71611926c055508b",
"comment_like" : "",
"comment_description" : "nice post"
}
]
}

You can use below aggregation:
db.post.aggregate([
{
$lookup: {
from: "user_profile_info",
let: { user_ids: "$post_likes.post_user_id" },
pipeline: [
{ $match: { $expr: { $in: [ "$user_id", "$$user_ids" ] } } },
{
$project: {
post_user_id: "$user_id",
image_url: 1,
user_name: 1
}
}
],
as: "users"
}
},
{
$project: {
_id: 1,
user_id: 1,
post_likes: {
$map: {
input: "$users",
as: "user",
in: {
post_user_id: "$$user.post_user_id",
image_url: "$$user.image_url",
user_name: "$$user.user_name",
like_status: {
$let: {
vars: {
like: {
$arrayElemAt: [
{ $filter: { input: "$post_likes", as: "pl", cond: { $eq: [ "$$pl.post_user_id", "$$user.post_user_id" ] } } }, 0
]
}
},
in: "$$like.like_status"
}
}
}
}
},
post_comments: 1
}
}
])
$lookup with custom pipeline (MongoDB 3.6 or newer) will allow you to get the data from user_profile_info for all users that are present in post_likes array. Then you need to "merge" users array with post_likes to get like_status. Since you have two arrays and you know that the same post_user_id appears in both of them you can use $map with $arrayElemAt and $filter to combine the data from both arrays.

Related

How to compare nested array elements with each other and count the total sub documents?

my mongodb document set look like this
{
"_id" : ObjectId("59093a8e1104a53169"),
"createdAt" : ISODate("2017-05-03T02:03:58.249+0000"),
"phone" : "0000000000",
"email" : "abc#gmail.com",
"dob" : "12/26/1976",
"password" : "*******",
"stripeID" : "***",
"picture" : "htt://g",
"name" : {
"first" : "P",
"last" : "e"
},
"addresses" : [
{
"description" : "237 S ABCD, USA",
"_id" : ObjectId("59093bsaaudua"),
"loc" : [
-008.2478742,
124.0517012
]
},
{
"apartment" : "",
"description" : "787 S Defghsvd USA",
"_id" : ObjectId("5a26b77dfhgswj"),
"loc" : [
-18.01,
34.039058
]
},
{
"description" : "13210 hdsg sdjhf 90284, USA",
"_id" : ObjectId("5d2482basasas17be1"),
"loc" : [
-18.01,
-18.01
]
}
]
}
what i need to do is compare loc[0] with loc[1] if addresses exists in the document and know how many of them has this x === y. i don't know how to approach this. any help would be great. thanks in advance.
i.e. what i want is in all the documents if any user has equal loc array element's, then i want to find those documents. my query should return like:
{
"description" : "13210 hdsg sdjhf 90284, USA",
"_id" : ObjectId("5d2482basasas17be1"),
"loc" : [
-18.01,
-18.01
]
}
this should do the trick:
db.collection.aggregate([
{
$unwind: '$addresses'
},
{
$match: {
$expr: {
$eq: [
{ $arrayElemAt: ["$addresses.loc", 0] },
{ $arrayElemAt: ["$addresses.loc", 1] }
]
}
}
},
{
$replaceRoot: {
newRoot: "$addresses"
}
}
])
https://mongoplayground.net/p/YRnbPm-qfe6
if you also want the count, you can do this:
db.collection.aggregate([
{
$unwind: '$addresses'
},
{
$match: {
$expr: {
$eq: [
{ $arrayElemAt: ["$addresses.loc", 0] },
{ $arrayElemAt: ["$addresses.loc", 1] }
]
}
}
},
{
$replaceRoot: {
newRoot: "$addresses"
}
},
{
$group: {
_id: null,
count: {
$sum: 1
},
addresses: {
$push: '$$ROOT'
}
}
},
{
$project: {
_id: 0
}
}
])
https://mongoplayground.net/p/Kqi4J7f-4go

add condition inside map in mongo script

This code is working fine. i am joining 2 different document but industryCategoryMapping have one array of object industryCategories . i want to add condition inside array of object.
db.brand.aggregate([
{ $lookup:
{
from: "industryCategoryMapping",
as: 'Category',
let: { entityId: { $toString : '$_id' }},
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ['$type', 'BRAND'] },
{ $eq: ['$$entityId', '$entityId'] },
]
}
}
}
]
}
}, {
$project: {
name: 1, industryCategoryMapping: {
$map:
{
input: "$Category",
as: "each",
in: '$$each.industryCategories'
}
}
}
},
])
and returns result
"_id" : ObjectId("5dfb18a6dfb32f87deeffa13"),
"name" : "Gatorade",
"industryCategoryMapping" : [
[
{
"_id" : ObjectId("5dd26e6e34d10718f843caa9"),
"isDefault" : true,
"industryCategory" : {
"_id" : ObjectId("5def644605ea8b5a7c97b48c"),
"industryCategory" : {
"_id" : ObjectId("5def644605ea8b5a7c97b48f")
}
}
},
{
"_id" : ObjectId("5dfb1514dfb32f87deeff97f"),
"isDefault" : false,
"industryCategory" : {
"_id" : ObjectId("5df8990c4d9c000b9c87592a"),
"industryCategory" : {
"_id" : ObjectId("5df8990c4d9c000b9c87592b")
}
}
}
]
]
}
But in given Json result industryCategoryMapping array have 2 object i need only isDefault : true type object . Can anyone help me to ignore isDefault : false object from given array.

How to $setDifference in array & Object using Mongo DB

UserDetails
{
"_id" : "5c23536f807caa1bec00e79b",
"UID" : "1",
"name" : "A",
},
{
"_id" : "5c23536f807caa1bec00e78b",
"UID" : "2",
"name" : "B",
},
{
"_id" : "5c23536f807caa1bec00e90",
"UID" : "3",
"name" : "C"
}
UserProducts
{
"_id" : "5c23536f807caa1bec00e79c",
"UPID" : "100",
"UID" : "1",
"status" : "A"
},
{
"_id" : "5c23536f807caa1bec00e79c",
"UPID" : "200",
"UID" : "2",
"status" : "A"
},
{
"_id" : "5c23536f807caa1bec00e52c",
"UPID" : "300",
"UID" : "3",
"status" : "A"
}
Groups
{
"_id" : "5bb20d7556db6915846da55f",
"members" : {
"regularStudent" : [
"200" // UPID
],
}
},
{
"_id" : "5bb20d7556db69158468878",
"members" : {
"regularStudent" : {
"0" : "100" // UPID
}
}
}
Step 1
I have to take UID from UserDetails check with UserProducts then take UPID from UserProducts
Step 2
we have to check this UPID mapped to Groups collection or not ?.
members.regularStudent we are mapped UPID
Step 3
Suppose UPID not mapped means i want to print the UPID from from UserProducts
I have tried but couldn't complete this, kindly help me out on this.
Expected Output:
["300"]
Note: Expected Output is ["300"] , because UserProducts having UPID 100 & 200 but Groups collection mapped only 100& 200.
My Code
var queryResult = db.UserDetails.aggregate(
{
$lookup: {
from: "UserProducts",
localField: "UID",
foreignField: "UID",
as: "userProduct"
}
},
{ $unwind: "$userProduct" },
{ "$match": { "userProduct.status": "A" } },
{
"$project": { "_id" : 0, "userProduct.UPID" : 1 }
},
{
$group: {
_id: null,
userProductUPIDs: { $addToSet: "$userProduct.UPID" }
}
});
let userProductUPIDs = queryResult.toArray()[0].userProductUPIDs;
db.Groups.aggregate([
{
$unwind: "$members.regularStudent"
},
{
$group: {
_id: null,
UPIDs: { $addToSet: "$members.regularStudent" }
}
},
{
$project: {
members: {
$setDifference: [ userProductUPIDs , "$UPIDs" ]
},
_id : 0
}
}
])
My Output
{
"members" : [
"300",
"100"
]
}
You need to fix that second aggregation and get all UPIDs as an array. To achieve that you can use $cond and based on $type either return an array or use $objectToArray to run the conversion, try:
db.Groups.aggregate([
{
$project: {
students: {
$cond: [
{ $eq: [ { $type: "$members.regularStudent" }, "array" ] },
"$members.regularStudent",
{ $map: { input: { "$objectToArray": "$members.regularStudent" }, as: "x", in: "$$x.v" } }
]
}
}
},
{
$unwind: "$students"
},
{
$group: {
_id: null,
UPIDs: { $addToSet: "$students" }
}
},
{
$project: {
members: {
$setDifference: [ userProductUPIDs , "$UPIDs" ]
},
_id : 0
}
}
])

Is it possible to join one field of an array unwind onto the unwound array?

Pretty new to mongo and haven't been able to figure out how to perform a query.
I have an accounts collection that looks like this:
{
"_id" : ObjectId("1"),
"time" : ISODate("2018-10-20T05:57:15.372Z"),
"profileId" : "1",
"totalUSD" : "1015.5513030613",
"accounts" : [
{
"_id" : ObjectId("2"),
"accountId" : "1",
"currency" : "USD",
"balance" : "530.7934159683763000",
"available" : "530.7934159683763",
"hold" : "0.0000000000000000",
"exchangeRateUSD" : "1"
},
{
"_id" : ObjectId("5"),
"accountId" : "4",
"currency" : "BTC",
"balance" : "0.0759214200000000",
"available" : "0.07592142",
"hold" : "0.0000000000000000",
"exchangeRateUSD" : "6384.995"
},
],
}
I store only exchangeRateUSD for each currency, and not exchangeRateXXX where XXX is currency name, because there can be an arbitrary number of currencies and currency pairs. But when I query the accounts collection it will always be queried by a currency pair, eg: BTC-USD. Keeping it simple for now, I can assume the currency pair will always be XXX-USD.
When I query the accounts collection I'd like to add a 'virtual' field to each account object: exchangeRateCrypto and then on the top-level accounts document I'd like to add totalCrypto which would just be the total account value in the given crypto. Eg: USD account balance * exchangeRateCrypto + crypto account balance * exchangeRateCrypto (which would equal 1).
My current query without the exchangeRateCrypto and totalCrypto looks like:
db.accounts.aggregate([
{ $unwind: '$accounts' },
{ $match: { 'accounts.currency': { $in: [ 'USD', 'BTC' ] }}},
{
$group: {
_id: '$_id',
time: { $first: '$time' },
profileId: { $first: '$profileId' },
accounts: { $push: '$accounts' },
totalUSD: { $sum: { $multiply: [ { $toDouble: '$accounts.balance' }, { $toDouble: '$accounts.exchangeRateUSD' } ] } }
}
}
]);
I'm trying to figure out how to 'reach' into the BTC row and calculate the exchangeRateCrypto by simply doing 1 / exchangeRateUSD and then projecting/returning the accounts document and subdocument as:
{
"_id" : ObjectId("1"),
"time" : ISODate("2018-10-20T05:57:15.372Z"),
"profileId" : "1",
"totalUSD" : "1015.5513030613",
"totalCrypto" : "0.1590527953", // 530.7934159683763 * 0.0001566171939 + 0.07592142 * 1
"accounts" : [
{
"_id" : ObjectId("2"),
"accountId" : "1",
"currency" : "USD",
"balance" : "530.7934159683763000",
"available" : "530.7934159683763",
"hold" : "0.0000000000000000",
"exchangeRateUSD" : "1",
"exchangeRateCrypto" : "0.0001566171939", // 1 / 6384.995
},
{
"_id" : ObjectId("5"),
"accountId" : "4",
"currency" : "BTC",
"balance" : "0.0759214200000000",
"available" : "0.07592142",
"hold" : "0.0000000000000000",
"exchangeRateUSD" : "6384.995",
"exchangeRateCrypto" : "1"
},
],
}
but haven't been able to figure out a good way of doing this.
It seems it should be pretty straightforward, but still learning Mongo.
Any tips?
Thanks!
The solution might be a bit long and probably it can be shortened however I want you to understand proposed way of thinking step by step.
var secondCurrency = "BTC";
var secondCurrencyFieldName = "exchangeRate" + secondCurrency;
var secondCurrencyFieldNameRef = "$" + secondCurrencyFieldName;
var totalFieldName = "total" + secondCurrency;
db.accounts.aggregate([
{ $unwind: "$accounts" },
{ $match: { "accounts.currency": { $in: [ "USD", secondCurrency ] }}},
{
$group: {
_id: "$_id",
time: { $first: "$time" },
profileId: { $first: "$profileId" },
accounts: { $push: "$accounts" },
totalUSD: { $sum: { $multiply: [ { $toDouble: "$accounts.balance" }, { $toDouble: "$accounts.exchangeRateUSD" } ] } }
}
},
{
$addFields: {
[secondCurrencyFieldName]: {
$filter: {
input: "$accounts",
as: "account",
cond: { $eq: [ "$$account.currency", secondCurrency ] }
}
}
}
},
{
$addFields: {
[secondCurrencyFieldName]: {
$let: {
vars: { first: { $arrayElemAt: [ secondCurrencyFieldNameRef, 0 ] } },
in: { $toDouble: "$$first.exchangeRateUSD" }
}
}
}
},
{
$addFields: {
accounts: {
$map: {
input: "$accounts",
as: "account",
in: {
$mergeObjects: [
"$$account",
{
[secondCurrencyFieldName]: {
$cond: [ { $eq: [ "$$account.currency", secondCurrency ] }, 1, { $divide: [ 1, secondCurrencyFieldNameRef ] } ]
}
}
]
}
}
}
}
},
{
$addFields: {
[totalFieldName]: {
$reduce: {
input: "$accounts",
initialValue: 0,
in: {
$add: [
"$$value",
{ $multiply: [ { $toDouble: "$$this.balance" }, "$$this." + secondCurrencyFieldName ] }
]
}
}
}
}
}
]).pretty()
So we can start with $addFields which can either add new field to existing document or repace existing field. After the $group stage you have to find the USD-XXX exchange rate (using $filter and $let + $arrayElemAt in the next pipeline stage). Having this value you can use $addFields again combined with $map and $mergeObjects to add new field to nested array and that field will represent the ratio between USD and XXX currency. Then you can use $addFields again with $reduce to get the total of all accounts for XXX currency.
Output:
{
"_id" : ObjectId("5beeec9fef99bb86541abf7f"),
"time" : ISODate("2018-10-20T05:57:15.372Z"),
"profileId" : "1",
"accounts" : [
{
"_id" : ObjectId("5beeec9fef99bb86541abf7d"),
"accountId" : "1",
"currency" : "USD",
"balance" : "530.7934159683763000",
"available" : "530.7934159683763",
"hold" : "0.0000000000000000",
"exchangeRateUSD" : "1",
"exchangeRateBTC" : 0.00015661719390539853
},
{
"_id" : ObjectId("5beeec9fef99bb86541abf7e"),
"accountId" : "4",
"currency" : "BTC",
"balance" : "0.0759214200000000",
"available" : "0.07592142",
"hold" : "0.0000000000000000",
"exchangeRateUSD" : "6384.995",
"exchangeRateBTC" : 1
}
],
"totalUSD" : 1015.5513030612763,
"exchangeRateBTC" : 6384.995,
"totalexchangeRateBTC" : 0.15905279535242806
}

MogoDB subdocument filteration

{
"_id" : ObjectId("5a4e43edb85ed11cd4dcba45"),
"email" : "av#gmail.com",
"username" : "alpesh",
"subscriptions" : [
{
"sub_id" : "5a4df654b9799b79147f9361",
"activation_date" : ISODate("2017-12-19T18:30:00.000Z"),
"expiry_date" : ISODate("2018-01-19T18:30:00.000Z")
},
{
"sub_id" : "5a4df654b9799b79147f9361",
"activation_date" : ISODate("2018-01-19T18:30:00.000Z"),
"expiry_date" : ISODate("2018-02-19T18:30:00.000Z")
},
{
"sub_id" : "5a51a925ddc5003b68cc38b3",
"activation_date" : ISODate("2018-02-19T18:30:00.000Z"),
"expiry_date" : ISODate("2018-03-22T18:30:00.000Z")
}
]
}
i have tried this..
db.find({"subscriptions.sub_id" : "5a4df654b9799b79147f9361" });
it returns ..
{
"_id" : ObjectId("5a4e43edb85ed11cd4dcba45"),
"email" : "av#gmail.com",
"username" : "alpesh",
"subscriptions" : [
{
"sub_id" : "5a4df654b9799b79147f9361",
"activation_date" : ISODate("2017-12-19T18:30:00.000Z"),
"expiry_date" : ISODate("2018-01-19T18:30:00.000Z")
},
{
"sub_id" : "5a4df654b9799b79147f9361",
"activation_date" : ISODate("2018-01-19T18:30:00.000Z"),
"expiry_date" : ISODate("2018-02-19T18:30:00.000Z")
},
{
"sub_id" : "5a51a925ddc5003b68cc38b3",
"activation_date" : ISODate("2018-02-19T18:30:00.000Z"),
"expiry_date" : ISODate("2018-03-22T18:30:00.000Z")
}
]
}
i have also tried $aggregate , $unwind , $filter , $projection and many ways but none of them returns as expected...
i want all the matching subdocuments...like this....
{
"_id" : ObjectId("5a4e43edb85ed11cd4dcba45"),
"email" : "av#gmail.com",
"username" : "alpesh",
"subscriptions" : [
{
"sub_id" : "5a4df654b9799b79147f9361",
"activation_date" : ISODate("2017-12-19T18:30:00.000Z"),
"expiry_date" : ISODate("2018-01-19T18:30:00.000Z")
},
{
"sub_id" : "5a4df654b9799b79147f9361",
"activation_date" : ISODate("2018-01-19T18:30:00.000Z"),
"expiry_date" : ISODate("2018-02-19T18:30:00.000Z")
}
]
}
db.collection('gyms').aggregate([
{
$match: {
subscriptions: {
$elemMatch: { sub_id: "5a4df654b9799b79147f9361" }
}
}
},
{
$redact: {
$cond: {
if: {
$or: [
{ $eq: ["$sub_id", "5a4df654b9799b79147f9361" ] },
{ $not: "$sub_id" }
]
},
then: "$$DESCEND",
else: "$$PRUNE"
}
}
}
])
this is generating as expected within a single doucment without unnecessary sub documents.
{
"email": "av#gmail.com",
"username": "alpesh",
"subscriptions": [
{
"sub_id": "5a4df654b9799b79147f9361",
"activation_date": "2017-12-19T18:30:00.000Z",
"expiry_date": "2018-01-19T18:30:00.000Z"
},
{
"sub_id": "5a4df654b9799b79147f9361",
"activation_date": "2018-01-19T18:30:00.000Z",
"expiry_date": "2018-02-19T18:30:00.000Z"
}
]
}
Two ways to achieve that. You can use $elemMatch as a projection operator but this will only bring you first matching subdocument:
db.collection.find(
{ "subscriptions.sub_id": "5a4df654b9799b79147f9361" },
{ "subscriptions" : { $elemMatch: { "sub_id": "5a4df654b9799b79147f9361" } } }
)
Second way is by using aggregation framework:
db.collection.aggregate([
{ $unwind: "$subscriptions" },
{ $match: {"subscriptions.sub_id": "5a4df654b9799b79147f9361"} },
{
$group: {
_id: "$_id",
"email" : { $first: "$email" },
"username" : { $first: "$username" },
"subscriptions": { $push: "$subscriptions" }
}
}
])
Here the point is that you need to group back your unwinded subscriptions to get back original shape (after filtering). To retrieve accumulate subscriptions into array you can use $push operator and to get back properties from outer document you can move them from any matching document (by using $first or $last).