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!
mongodb mongodb-query aggregation-framework
add a comment |
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!
mongodb mongodb-query aggregation-framework
add a comment |
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!
mongodb mongodb-query aggregation-framework
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!
mongodb mongodb-query aggregation-framework
mongodb mongodb-query aggregation-framework
edited Nov 16 '18 at 9:07
chridam
68.4k16114148
68.4k16114148
asked Nov 16 '18 at 3:24
lostdorjelostdorje
2,77363068
2,77363068
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
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
Much appreciated. Very useful, thanks for laying it out step by step!
– lostdorje
Nov 19 '18 at 0:18
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53330969%2fis-it-possible-to-join-one-field-of-an-array-unwind-onto-the-unwound-array%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
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
Much appreciated. Very useful, thanks for laying it out step by step!
– lostdorje
Nov 19 '18 at 0:18
add a comment |
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
Much appreciated. Very useful, thanks for laying it out step by step!
– lostdorje
Nov 19 '18 at 0:18
add a comment |
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
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
answered Nov 16 '18 at 16:49
micklmickl
15k51639
15k51639
Much appreciated. Very useful, thanks for laying it out step by step!
– lostdorje
Nov 19 '18 at 0:18
add a comment |
Much appreciated. Very useful, thanks for laying it out step by step!
– lostdorje
Nov 19 '18 at 0:18
Much appreciated. Very useful, thanks for laying it out step by step!
– lostdorje
Nov 19 '18 at 0:18
Much appreciated. Very useful, thanks for laying it out step by step!
– lostdorje
Nov 19 '18 at 0:18
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53330969%2fis-it-possible-to-join-one-field-of-an-array-unwind-onto-the-unwound-array%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown