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