How to use group by in mongodb? - mongodb

I want to values in comma seperated.So how to use group/concat in aggregate framework?
Document contact
{
"_id" : "9c612c95054fb46e3de8dee8",
"name" : "name1",
"oppID" : [
"5c612c95054fb46e3de8bcc5",
"5bd6b334cba7d2241a3ba9d9"
]
},
{
"_id" : "1c612c95054fb46e3de8dcde9",
"name" : "name2",
"oppID" : [
"5c612c95054fb46e3de8bcc5",
"5bd6b334cba7d2241a3ba9d9"
]
}
Document opportunity
{
"_id" : "5c612c95054fb46e3de8bcc5",
"name" : "opp name 01",
},
{
"_id" : "5bd6b334cba7d2241a3ba9d9",
"name" : "opp name 02",
},
I want to get contact list like below,
{
"_id" : "9c612c95054fb46e3de8dee8",
"oppName" : "opp name 01, opp name 02"
},
{
"_id" : "1c612c95054fb46e3de8dcde9",
"oppName" : "opp name 01, opp name 02"
}

You need to run $lookup to get the data from both collections and then $reduce with $concat to transform an array into single string
db.contact.aggregate([
{
$lookup: {
from: "opportunity",
localField: "oppID",
foreignField: "_id",
as: "opportunities"
}
},
{
$project: {
_id: 1,
oppName: {
$reduce: {
input: "$opportunities",
initialValue: "",
in: {
$cond: [ { $eq: [ "$$value", "" ] }, "$$this.name", { $concat: [ "$$value", ", ", "$$this.name" ] } ]
}
}
}
}
}
])

Related

MongoDB Aggregate two collections and flatten the second collection

Collection - 1 (users)
{
"_id" : "aandtv10#gmail.com",
"updateTimestamp" : "2022-07-19T11:59:18.029Z",
"userConsent" : null,
"firstName" : "Aand",
"lastName" : "Aa",
"fullName" : "Aand Aa",
"orgRootName" : "Black&Veatch",
"relationType" : "Contractor"
}
Collection -2 (2 documents)
/* 1 */
{
"_id" : ObjectId("62e9296bf06b3bfdc93be0da"),
"lastUpdated" : "2022-08-02T13:40:59.265Z",
"email" : "aandtv10#gmail.com",
"userName" : "Aand Aa",
"docType" : "dose2",
"administeredTimestamp" : "2022-10-02T11:10:55.784Z",
"uploadTimestamp" : "2022-08-02T13:40:59.265Z",
"manufacturer" : "Novavax"
}
/* 2 */
{
"_id" : ObjectId("62e911cf5b6e67c36c4f843e"),
"lastUpdated" : "2022-08-02T12:00:15.201Z",
"email" : "aandtv10#gmail.com",
"userName" : "Aand Aa",
"templateName" : "Covid-Vaccine",
"docType" : "dose1",
"exemptType" : null,
"administeredTimestamp" : "2022-08-23T17:16:20.347Z",
"uploadTimestamp" : "2022-08-02T12:00:15.202Z",
"manufacturer" : "Novavax"
}
Expected Result:
{
"_id" : "aandtv10#gmail.com",
"updateTimestamp" : "2022-07-19T11:59:18.029Z",
"firstName" : "Aand",
"lastName" : "Aa",
"fullName" : "Aand Aa",
"orgRootName" : "Black&Veatch",
"relationType" : "Contractor",
"dose1_administeredTimestamp" : "2022-08-23T17:16:20.347Z",
"dose1_manufacturer" : "Novavax",
"dose2_administeredTimestamp" : "2022-10-02T11:10:55.784Z",
"dose2_manufacturer" : "Novavax"
}
Query Used:
db.users.aggregate([{
"$match": {
"_id": "aandtv10#gmail.com"
}
},{
$lookup: {
from: "documents",
localField: "_id",
foreignField: "email",
as: "documents"
}}, { $unwind: "$documents" },{
"$group": {
"fullName": { $max: "$identity.fullName" },
"_id":"$_id",
"relationship": { $max:"$seedNetwork.relationType" },
"registration_date": { $max:"$seedNetwork.seededTimestamp" },
"vaccination_level": { $max: "" },
"exemption_declination_date": { $max:"N/A" },
"exemption_verification": { $max: "N/A" },
"dose1_date" : { $max: { $cond: { if: {$eq: ["$docType", "dose1" ]}, then: "$administeredTimestamp", else: "<false-case>" } } }
}
}
]);
One option is using $reduce with $arrayToObject:
db.users.aggregate([
{$match: {_id: "aandtv10#gmail.com"}},
{$set: {data: "$$ROOT"}},
{$lookup: {
from: "documents",
localField: "_id",
foreignField: "email",
as: "documents"
}
},
{$project: {
_id: 0,
data: 1,
documents: {
$reduce: {
input: "$documents",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[
{k: {$concat: ["$$this.docType", "_administeredTimestamp"]},
v: "$$this.administeredTimestamp"},
{k: {$concat: ["$$this.docType", "_manufacturer"]},
v: "$$this.manufacturer"}
]
]
}
}
}
}
},
{$replaceRoot: {newRoot: {$mergeObjects: ["$data", {$arrayToObject: "$documents"}]}}}
])
See how it works on the playground example

MongoDb: find deeply nested object with $lookup

I have collection like this:
collection name is - account.
and it has sub-documents like account > buildings > gateways > devices.
{
"_id" : ObjectId("5e1fe45cd05bfb0cc549297d"),
"apiCallCount" : 0,
"email" : "info#data.com",
"password" : "dummy",
"userName" : "AAAAA",
"companyName" : "The AAAAAA",
"apiKey" : "5e1fe45cd05bfb0cc549297c",
"solutionType" : "VVVVVV",
"parentCompany" : "",
"buildings" : [
{
"_id" : ObjectId("5e1fe5e3d05bfb0cc5494146"),
"buildingName" : "xxxxxx",
"address" : "xxx",
"suite" : "101",
"floor" : "22",
"timeZone" : "us/eastern",
"gateways" : [
{
"_id" : ObjectId("5e1fe624d05bfb0cc549453a"),
"gatewayName" : "CC-GW-THF-001",
"gatewayKey" : "gk_5e1fe624d05bfb0cc549453a",
"suite" : "area1",
"devices" : [
{
"_id" : ObjectId("5e1fe751d05bfb0cc549578d"),
"serialNumber" : "129300000013",
"area" : "area1",
"connectionStatus" : 1,
"gatewayKey" : "gk_5e1fe624d05bfb0cc549453a",
"applicationNumber" : 30,
"firmwareVersion" : "1.0",
"needsAttention" : false,
"verificationCode" : "GAAS",
"createdAt" : ISODate("2020-01-16T04:32:17.899Z"),
"updatedAt" : ISODate("2020-01-16T08:53:54.460Z")
}
],
"createdAt" : ISODate("2020-01-16T04:27:16.678Z"),
"updatedAt" : ISODate("2020-01-16T08:53:54.460Z")
},
{
"_id" : ObjectId("5e1fe651d05bfb0cc54947f0"),
"gatewayName" : "AA-GW-THF-002",
"gatewayKey" : "gk_5e1fe651d05bfb0cc54947f0",
"suite" : "area2",
"devices" : [
{
"_id" : ObjectId("5e1fe7a9d05bfb0cc5495cdf"),
"serialNumber" : "129300000012",
"area" : "area2",
"connectionStatus" : 0,
"gatewayKey" : "gk_5e1fe651d05bfb0cc54947f0",
"applicationNumber" : 30,
"firmwareVersion" : "1.0",
"needsAttention" : false,
"verificationCode" : "VG3K",
"createdAt" : ISODate("2020-01-16T04:33:45.698Z"),
"updatedAt" : ISODate("2020-01-16T08:54:17.604Z")
}
],
"createdAt" : ISODate("2020-01-16T04:28:01.532Z"),
"updatedAt" : ISODate("2020-01-16T08:54:17.604Z")
},
],
"createdAt" : ISODate("2020-01-16T04:26:11.941Z"),
"updatedAt" : ISODate("2020-01-16T08:56:32.657Z")
}
],
"createdAt" : ISODate("2020-01-16T04:19:40.310Z"),
"updatedAt" : ISODate("2020-04-06T18:18:39.628Z"),
"__v" : 1,
}
I have accountId, buildingId, gatewayId, deviceId.
I am trying to find matched device object using $lookup operator.
I think I have to fist find the building object by using buildingId and then filter gateway under that building using gatewayId and then find device object using deviceId that I have.
I basically need to have access to device object fields to project in final output.
Having difficulty in coming up with correct pipleline for usingg lookup operator.
I have this so far:
db.getCollection('test').aggregate([
{
$lookup: {
from: 'account',
let: {
accountId: "$accountId"
},
pipeline: [
{
"$match": {
"$expr": {
"$eq": ["$_id", "$$accountId"]
}
}
},
],
as: "accountDetails"
}
}, {
$unwind: "$accountDetails"
}, {
$lookup: {
from: 'account',
let: {
accountId: "$accountId",
buildingId: "$buildingId",
buildings: "$accountDetails"
},
pipeline: [
{
"$match": {
"$expr": {
"$eq": ["$buildings._id", "$$buildingId"] // how to dig through nested document to get to devices ?
}
}
},
],
as: "buildingDetails"
}
}
{
$project: { ... ...
}
])
if I do this:
{
$lookup: {
from: 'account',
localField: "accountId",
foreignField: "_id",
as: "accountDetails"
}
},
accountDetails gives me access the account document based on accountId. but I need to get to buildings > gateways > devices and find the matching device.
UPDATE:
I forgot to mention, I am working with 2 collections here.
sensingresults and accounts.
Main purpose is to aggregate data from sensingresults, but also find deviceId from account collection and return with the result.
That's why lookup is needed to join 2 collections?
UPDATE2:
Current output:
{
"accountId": ObjectId("5e1fe45cd05bfb0cc549297d"),
"avgZoneCountNumber": 0,
"avgZoneCountNumberInstant": 0,
"buildingId": ObjectId("5e1fe5e3d05bfb0cc5494146"),
"companyName": "The AAAAAA",
"createdAt": ISODate("1970-01-01T00:00:00Z"),
"dateHour": "2020-03-19T18",
"deviceId": ObjectId("5e1fe81ed05bfb0cc5496406"),
"gatewayId": ObjectId("5e1fe6a6d05bfb0cc5494d25"),
"minuteBucket": 1
}
Expected result:
{
"accountId": ObjectId("5e1fe45cd05bfb0cc549297d"),
"avgZoneCountNumber": 0,
"avgZoneCountNumberInstant": 0,
"buildingId": ObjectId("5e1fe5e3d05bfb0cc5494146"),
"createdAt": ISODate("1970-01-01T00:00:00Z"),
"dateHour": "2020-03-19T18",
"deviceId": ObjectId("5e1fe81ed05bfb0cc5496406"),
"gatewayId": ObjectId("5e1fe6a6d05bfb0cc5494d25"),
"minuteBucket": 1,
"serialNumber: 1, // this value should come from device object
"area": 1 // this value should come from device object
}
You can find nested device using $filter, $arrayElemAt and $let:
device: {
$let: {
vars: {
building: {
$arrayElemAt: [ { $filter: { input: "$company_name.buildings", cond: { $eq: [ "$$this._id", "$buildingId" ] }} }, 0 ]
}
},
in: {
$let: {
vars: {
gateway: {
$arrayElemAt: [ { $filter: { input: "$$building.gateways", cond: { $eq: [ "$$this._id", "$gatewayId" ] }} }, 0 ]
}
},
in: { $arrayElemAt: [ { $filter: { input: "$$gateway.devices", cond: { $eq: [ "$$this._id", "$deviceId" ] }} }, 0 ] }
}
}
}
}
Full Solution

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

mongodb aggregation with array and lookup

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.

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
}