MongoDB Aggregation with match shows all the records - mongodb

I have json object as following.
{
"_id" : ObjectId("123209sfekjern"),
"Name" : "Test1",
"Orders" : [
{
"Date" : "2020-05-05",
"Total" : "100.00"
},
{
"Date" : "2020-05-10",
"Total" : "110.00"
},
{
"Date" : "2020-05-11",
"Total" : "100.00"
},
{
"Date" : "2020-05-14",
"Total" : "110.00"
},
{
"Date" : "2020-05-20",
"Total" : "100.00"
},
{
"Date" : "2020-05-15",
"Total" : "100.00"
},
{
"Date" : "2020-05-12",
"Total" : "110.00"
},
{
"Date" : "2020-05-18",
"Total" : "100.00"
},
{
"Date" : "2020-05-31",
"Total" : "110.00"
}
]
}
I need customername, orders.Date and Order.Total for all the orders which is greater than 100.00
I tried following query..
db.Customers.aggregate
(
[
{
$match: {
$and: [
{"Orders.Date":{$gte:"2020-05-15"}},//ISODate('2020-05-15 10:00:00.000Z')
{ "Orders.Total": { $gte: "100.00" } },
]
}
},
{ $project: { _id:0, Name: 1, "Orders.Total": 1, "Orders.Date": 1} },
]
)
The above query returns all the records. I m still beginner and learning mongodb.
any help would be appreciated.
thank you.

$match filters on a document level so entire document will be returned if at least one subdocument matches your conditions. In order to filter a nested array you need $filter:
db.Customers.aggregate([
{
$project: {
_id: 0,
Name: 1,
Orders: {
$filter: {
input: "$Orders",
cond: {
$and: [
{ $gte: [ "$$this.Date", "2020-05-15" ] },
{ $gte: [ "$$this.Total", "100.00" ] },
]
}
}
}
}
}
])
Mongo Playground

Related

MongoDB - Group by and count value, but treat per record as one

I want to group by and count follow_user.tags.tag_id per record, so no matter how many times the same tag_id show up on the same record, it only counts as 1.
My database structure looks like this:
{
"external_userid" : "EXID1",
"follow_user" : [
{
"userid" : "USERID1",
"tags" : [
{
"tag_id" : "TAG1"
}
]
},
{
"userid" : "USERID2",
"tags" : [
{
"tag_id" : "TAG1"
},
{
"tag_id" : "TAG2"
}
]
}
]
},
{
"external_userid" : "EXID2",
"follow_user" : [
{
"userid" : "USERID1",
"tags" : [
{
"tag_id" : "TAG2"
}
]
}
]
}
Here's my query:
[
{ "$unwind": "$follow_user" }, { "$unwind": "$follow_user.tags" },
{ "$group" : { "_id" : { "follow_user᎐tags᎐tag_id" : "$follow_user.tags.tag_id" }, "COUNT(_id)" : { "$sum" : 1 } } },
{ "$project" : { "total" : "$COUNT(_id)", "tagId" : "$_id.follow_user᎐tags᎐tag_id", "_id" : 0 } }
]
What I expected:
{
"total" : 1,
"tagId" : "TAG1"
},
{
"total" : 2,
"tagId" : "TAG2"
}
What I get:
{
"total" : 2,
"tagId" : "TAG1"
},
{
"total" : 2,
"tagId" : "TAG2"
}
$set - Create a new field follow_user_tags.
1.1. $setUnion - To distinct the value from the Result 1.1.1.
1.1.1. $reduce - Add the value of follow_user.tags.tag_id into array.
$unwind - Deconstruct follow_user_tags array field to multiple documents.
$group - Group by follow_user_tags and perform total count via $sum.
$project - Decorate output document.
db.collection.aggregate([
{
$set: {
follow_user_tags: {
$setUnion: {
"$reduce": {
"input": "$follow_user.tags",
"initialValue": [],
"in": {
"$concatArrays": [
"$$value",
"$$this.tag_id"
]
}
}
}
}
}
},
{
$unwind: "$follow_user_tags"
},
{
$group: {
_id: "$follow_user_tags",
total: {
$sum: 1
}
}
},
{
$project: {
_id: 0,
tagId: "$_id",
total: 1
}
}
])
Sample Mongo Playground

How to get the recent values of grouped result?

Below is the document which has an array name datum and I want to filter the records based on StatusCode, group by Year and sum the amount value from the recent record of distinct Types.
{
"_id" : ObjectId("5fce46ca6ac9808276dfeb8c"),
"year" : 2018,
"datum" : [
{
"StatusCode" : "A",
"Type" : "1",
"Amount" : NumberDecimal("100"),
"Date" : ISODate("2018-05-30T00:46:12.784Z")
},
{
"StatusCode" : "A",
"Type" : "1",
"Amount" : NumberDecimal("300"),
"Date" : ISODate("2023-05-30T00:46:12.784Z")
},
{
"StatusCode" : "A",
"Type" : "2",
"Amount" : NumberDecimal("420"),
"Date" : ISODate("2032-05-30T00:46:12.784Z")
},
{
"StatusCode" : "B",
"Type" : "2",
"Amount" : NumberDecimal("420"),
"Date" : ISODate("2032-05-30T00:46:12.784Z")
}
]
}
In my case following is the expected result :
{
Total : 720
}
I want to achieve the result in the following aggregate Query pattern
db.collection.aggregate([
{
$addFields: {
datum: {
$reduce: {
input: "$datum",
initialValue: {},
"in": {
$cond: [
{
$and: [
{ $in: ["$$this.StatusCode", ["A"]] }
]
},
"$$this",
"$$value"
]
}
}
}
}
},
{
$group: {
_id: "$year",
RecentValue: { $sum: "$datum.Amount" }
}
}
])
You can first $unwind the datum array. Do the filtering and sort by the date. Then get the record with latest datum by a $group. Finally do another $group to calculate the sum.
Here is a mongo playground for your reference.

Mongodb find maximum scored student embeddded array

I am new in mongodb ,Please help me out
I have more than 500 students details like this..
{
"_id" : 7,
"name" : "Salena Olmos",
"scores" : [
{
"score" : 90.37826509157176,
"type" : "exam"
},
{
"score" : 42.48780666956811,
"type" : "quiz"
},
{
"score" : 96.52986171633331,
"type" : "homework"
}
]
},
/* 2 */
{
"_id" : 8,
"name" : "Daphne Zheng",
"scores" : [
{
"score" : 22.13583712862635,
"type" : "exam"
},
{
"score" : 14.63969941335069,
"type" : "quiz"
},
{
"score" : 75.94123677556644,
"type" : "homework"
}
]
}
Need to find one student details who got highest marks in "type" exam
Output as follows...
{
"_id" : 7,
"name" : "Salena Olmos",
"scores" : [
{
"score" : 90.37826509157176,
"type" : "exam"
},
{
"score" : 42.48780666956811,
"type" : "quiz"
},
{
"score" : 96.52986171633331,
"type" : "homework"
}
]
}
I need one student details from whole collection. The problem I am facing that need to search in embedded array "score" as well as "type".
Someone please help me
Try this
db.collection.aggregate([
{
$group: {
_id: "$_id",
scores: {
$first: "$scores"
},
data: {
$push: "$$ROOT"
}
}
},
{
$unwind: "$data"
},
{
$match: {
"data.scores.type": "exam"
}
},
{
$sort: {
"data.scores.score": -1
}
},
{
$project: {
_id: 1,
name: "$data.name",
scores: "$scores"
}
},
{
$limit: 1
}
])
Sample Playground
While this doesn't answer the question, it is related. This one filters out all the subdocuments which match the conditions "greater or equal 90" and type "exam"
db.collection.aggregate([
{
$match: {
"scores.score": {
$gte: 90
},
"scores.type": "exam"
}
},
{
$project: {
name: true,
list: {
$filter: {
input: "$scores",
as: "list",
cond: {
$and: [
{
$gt: [
"$$list.score",
90
]
},
{
$eq: [
"$$list.type",
"exam"
]
}
]
}
}
}
}
}
])
which returns
[
{
"_id": 7,
"list": [
{
"score": 90.37826509157176,
"type": "exam"
}
],
"name": "Salena Olmos"
}
]
https://mongoplayground.net/p/hYnVzZbuNFI
If you want the entire document, then add doc: "$$ROOT", to the projection.

How to get percentage total of data with group by date in MongoDB

How to get percentage total of data with group by date in MongoDB ?
Link example : https://mongoplayground.net/p/aNND4EPQhcb
I have some collection structure like this
{
"_id" : ObjectId("5ccbb96706d1d47a4b2ced4b"),
"date" : "2019-05-03T10:39:53.108Z",
"id" : 166,
"update_at" : "2019-05-03T10:45:36.208Z",
"type" : "image"
}
{
"_id" : ObjectId("5ccbb96706d1d47a4b2ced4c"),
"date" : "2019-05-03T10:39:53.133Z",
"id" : 166,
"update_at" : "2019-05-03T10:45:36.208Z",
"type" : "image"
}
{
"_id" : ObjectId("5ccbb96706d1d47a4b2ced4d"),
"date" : "2019-05-03T10:39:53.180Z",
"id" : 166,
"update_at" : "2019-05-03T10:45:36.208Z",
"type" : "image"
}
{
"_id" : ObjectId("5ccbb96706d1d47a4b2ced4e"),
"date" : "2019-05-03T10:39:53.218Z",
"id" : 166,
"update_at" : "2019-05-03T10:45:36.208Z",
"type" : "image"
}
And I have query in mongodb to get data of collection, how to get percentage of total data. in bellow example query to get data :
db.name_collection.aggregate(
[
{ "$match": {
"update_at": { "$gte": "2019-11-04T00:00:00.0Z", "$lt": "2019-11-06T00:00:00.0Z"},
"id": { "$in": [166] }
} },
{
"$group" : {
"_id": {
$substr: [ '$update_at', 0, 10 ]
},
"count" : {
"$sum" : 1
}
}
},
{
"$project" : {
"_id" : 0,
"date" : "$_id",
"count" : "$count"
}
},
{
"$sort" : {
"date" : 1
}
}
]
)
and this response :
{
"date" : "2019-11-04",
"count" : 39
},
{
"date" : "2019-11-05",
"count" : 135
}
how to get percentage data total from key count ? example response to this :
{
"date" : "2019-11-04",
"count" : 39,
"percentage" : "22%"
},
{
"date" : "2019-11-05",
"count" : 135,
"percentage" : "78%"
}
You have to group by null to get total count and then use $map to calculate the percentage. $round will be a useful operator in such case. Finally you can $unwind and $replaceRoot to get back the same number of documents:
db.collection.aggregate([
// previous aggregation steps
{
$group: {
_id: null,
total: { $sum: "$count" },
docs: { $push: "$$ROOT" }
}
},
{
$project: {
docs: {
$map: {
input: "$docs",
in: {
date: "$$this.date",
count: "$$this.count",
percentage: { $concat: [ { $toString: { $round: { $multiply: [ { $divide: [ "$$this.count", "$total" ] }, 100 ] } } }, '%' ] }
}
}
}
}
},
{
$unwind: "$docs"
},
{
$replaceRoot: { newRoot: "$docs" }
}
])
Mongo Playground

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
}