How to do pivoting on MongoDB? - database

I have this 'Sales' collection and a sample of it looks like this:
[
{cusID: 'a412q39x',
cusCountry: 'MEX',
itemPurchased: 'Toy_A'
},
{cusID: 'r760e11s',
cusCountry: 'USA',
itemPurchased: 'Toy_B'
},
{cusID: 'g723f01z',
cusCountry: 'USA',
itemPurchased: 'Toy_C'
},
{cusID: 'h277p01c',
cusCountry: 'CAN',
itemPurchased: 'Toy_B'
}
]
This is the result I am hoping to achieve.
[
{item: 'Toy_A',
USA: 4,
MEX: 2,
CAN: 1,
BRA: 0
},
{item: 'Toy_B',
USA: 3,
MEX: 0,
CAN: 2,
BRA: 1
}
]
I tried:
{
$group:{_id:{toy:'$itemPurchased', country: $cusCountry'},'cnt':{'$sum': 1}}
}
The result was not what I wanted.
[
{
_id.toy: 'Toy_A',
_id.country: 'BRA',
cnt: 43
},
{
_id.toy: 'Toy_A',
_id.country: 'USA',
cnt: 102
},
{
_id.toy: 'Toy_A',
_id.country: 'JPN',
cnt: 72
},
{
_id.toy: 'Toy_B',
_id.country: 'CAN',
cnt: 32
}
]
I have also experimented with $facet but to no avail. Mongo gurus, please enlighten. Thanks in advance.

Try as below:
db.collection.aggregate([
{
$group: { _id: '$itemPurchased' , items: { $push: { "country" : "$cusCountry", "count": { $sum :1} } } }
},
{
"$project": {
"countryCounts": {
"$arrayToObject": {
"$map": {
"input": "$items",
"as": "item",
"in": {
"k": "$$item.country",
"v": "$$item.count",
}
}
}
}
}
},
{ $replaceRoot: { newRoot: { "$mergeObjects":[ "$countryCounts" , { "item":"$_id"} ] } } }
])
You will get the result like below:
{
"USA" : 1,
"item" : "Toy_C"
},
/* 2 */
{
"USA" : 1,
"CAN" : 1,
"item" : "Toy_B"
},
/* 3 */
{
"MEX" : 1,
"item" : "Toy_A"
}

Related

Count of equal elements in array across multiple documents in mongodb

I'm working on simple program that counts total number of special units through n number of players.
I have documents similar to this (simplified), where array rosterUnits could be of length 0 to 7. There is a total of 7 special units. I need to know how many of each unit players have in roster.
{
{
_id: ObjectId(...),
member: {
rosterUnits: [ "Unit1", "Unit2", "Unit3", "Unit4"]
}
},
{
_id: ObjectId(...),
member: {
rosterUnits: [ "Unit1", "Unit3"]
}
},
...
}
Expected result would be something like this:
{
_id: ...
result: [
{
name: "Unit1"
count: 2
},
{
name: "Unit2"
count: 1
},
{
name: "Unit3"
count: 2
},
...
{
name: "Unit7"
count: 0
}
]
}
How do I achieve this using aggregate pipeline?
EDIT (2/7/2023)
Excuse me everyone, I thought I provided enough details here but...
Documents are very big and pipeline until this stage is very long.
I wanted to spare you the trouble with the documents
I have guild with up to 50 players. I search for guild then $unwind members of guild and $lookup into members to get member.rosterUnit(s).
This is a full query I came up with:
db.getCollection('guilds').aggregate([
{ $match: { 'profile.id': 'jrl9Q-_CRDGdMyNjTQH1rQ' } },
//{ $match: { 'profile.id': { $in : ['jrl9Q-_CRDGdMyNjTQH1rQ', 'Tv_j9nhRTgufvH7C7oUYAA']} } },
{ $project: { member: 1, profile: 1 } },
{ $unwind: "$member" },
{
$lookup: {
from: "players",
localField: "member.playerId",
foreignField: "playerId",
pipeline: [
{
$project: {
profileStat: 1,
rosterUnit: {
$let: {
vars: { gls: ["JABBATHEHUTT:SEVEN_STAR", "JEDIMASTERKENOBI:SEVEN_STAR", "GRANDMASTERLUKE:SEVEN_STAR", "LORDVADER:SEVEN_STAR", "GLREY:SEVEN_STAR", "SITHPALPATINE:SEVEN_STAR", "SUPREMELEADERKYLOREN:SEVEN_STAR"], },
in: {
$reduce: {
input: "$rosterUnit",
initialValue: [],
in: {
$cond: {
if: { $gt: [{ $indexOfArray: ["$$gls", "$$this.definitionId"] }, -1] },
then: { $concatArrays: ["$$value", [{ definitionId: "$$this.definitionId", count: 1 }]] },
else: { $concatArrays: ["$$value", []] }
}
},
}
}
}
}
}
}
],
as: "member"
}
},
{
$addFields: {
member: { $arrayElemAt: ["$member", 0] },
gpStats: {
$let: {
vars: { member: { $arrayElemAt: ["$member", 0] } },
in: {
$reduce: {
input: "$$member.profileStat",
initialValue: {},
in: {
characterGp: {
$arrayElemAt: [
"$$member.profileStat.value",
{
$indexOfArray: [
"$$member.profileStat.nameKey",
"STAT_CHARACTER_GALACTIC_POWER_ACQUIRED_NAME"
]
}
]
},
shipGp: {
$arrayElemAt: [
"$$member.profileStat.value",
{
$indexOfArray: [
"$$member.profileStat.nameKey",
"STAT_SHIP_GALACTIC_POWER_ACQUIRED_NAME"
]
}
]
}
}
}
}
}
}
}
},
{
$group: {
_id: "$profile.id",
guildName: { $first: "$profile.name" },
memberCount: { $first: "$profile.memberCount" },
guildGp: { $first: "$profile.guildGalacticPower" },
totalGp: { $sum: { $sum: [{ $toInt: "$gpStats.characterGp" }, { $toInt: "$gpStats.shipGp" }] } },
avgTotalGp: { $avg: { $sum: [{ $toInt: "$gpStats.characterGp" }, { $toInt: "$gpStats.shipGp" }] } },
characterGp: { $sum: { $toInt: "$gpStats.characterGp" } },
shipGp: { $sum: { $toInt: "$gpStats.shipGp" } },
}
}
])
I want to add new field in group with desired result from above.
If I do $unwind on member.rosterUnit how do I go back to member grouping?
(Excuse me once again, this is my first question)
Use $unwind to deconstruct the rosterUnits array into separate documents.
Then use $group to group the documents by the rosterUnits values and calculate the count for each unit.
Then use $project to format the output to include only the name and count fields.
db.collection.aggregate([
{
$unwind: "$member.rosterUnits"
},
{
$group: {
_id: "$member.rosterUnits",
count: { $sum: 1 }
}
},
{
$project: {
_id: 0,
name: "$_id",
count: "$count"
}
}
])
Yes I think that the best way of do that is using aggregations.
I'm sure there is a better way to do it.
But here is the solution, I hope it works for you friend.
Basically we are going to use a "$group" aggregation and within it using an operator "$cond" and "$in" we are going to validate case by case if the searched element is found. In the case that it is so, we will add one and if the element is not found, zero.
I recommend you download mongodb compass to try it
Aggregation:
[{
$group: {
_id: null,
Unit1: {
$sum: {
$cond: [
{
$in: [
'Unit1',
'$member.rosterUnits'
]
},
1,
0
]
}
},
Unit2: {
$sum: {
$cond: [
{
$in: [
'Unit2',
'$member.rosterUnits'
]
},
1,
0
]
}
},
Unit3: {
$sum: {
$cond: [
{
$in: [
'Unit3',
'$member.rosterUnits'
]
},
1,
0
]
}
},
Unit4: {
$sum: {
$cond: [
{
$in: [
'Unit4',
'$member.rosterUnits'
]
},
1,
0
]
}
},
Unit5: {
$sum: {
$cond: [
{
$in: [
'Unit5',
'$member.rosterUnits'
]
},
1,
0
]
}
},
Unit6: {
$sum: {
$cond: [
{
$in: [
'Unit6',
'$member.rosterUnits'
]
},
1,
0
]
}
},
Unit7: {
$sum: {
$cond: [
{
$in: [
'Unit7',
'$member.rosterUnits'
]
},
1,
0
]
}
}
}
}, {
$project: {
_id: 0
}
}]
Query
because you want to count values that might not exists, you can make the groups manualy, and do conditional count
after the group you can do extra tranformation(if you really need the expected outpute exactly like that). Object to array, and map to give the field names(name,count)
Playmongo
aggregate(
[{"$unwind": "$member.rosterUnits"},
{"$group":
{"_id": null,
"Unit1":
{"$sum":
{"$cond": [{"$eq": ["$member.rosterUnits", "Unit1"]}, 1, 0]}},
"Unit2":
{"$sum":
{"$cond": [{"$eq": ["$member.rosterUnits", "Unit2"]}, 1, 0]}},
"Unit3":
{"$sum":
{"$cond": [{"$eq": ["$member.rosterUnits", "Unit3"]}, 1, 0]}},
"Unit4":
{"$sum":
{"$cond": [{"$eq": ["$member.rosterUnits", "Unit4"]}, 1, 0]}},
"Unit5":
{"$sum":
{"$cond": [{"$eq": ["$member.rosterUnits", "Unit5"]}, 1, 0]}},
"Unit6":
{"$sum":
{"$cond": [{"$eq": ["$member.rosterUnits", "Unit6"]}, 1, 0]}},
"Unit7":
{"$sum":
{"$cond": [{"$eq": ["$member.rosterUnits", "Unit7"]}, 1, 0]}}}},
{"$unset": ["_id"]},
{"$project":
{"result":
{"$map":
{"input": {"$objectToArray": "$$ROOT"},
"in": {"name": "$$this.k", "count": "$$this.v"}}}}}])

MongoDB - Geometry object to return the total pairs/points in linear ring

I am trying to write a performance report based on shapefile data I have stored within docs stored in collections.
Here is a sample of data:
The following function works quite well as it returns the amount of bytes for each document - great, however I would also like to know how many points/pairs are stored within each polygon's linear string for each document.
db.getCollection("_collectionName").aggregate([{"$project": {"object_size": { $bsonSize: "$$ROOT" }}}])
This returns the following set of data (sample):
{ _id: ObjectId("5ef7da26ae8659149c97657e"), rootSize: 42215 },
{ _id: ObjectId("5ef7da45ae8659149c97657f"), rootSize: 118574 },
{ _id: ObjectId("5ef7daf1ae8659149c976585"), rootSize: 11886 },
{ _id: ObjectId("5f216685dbef0f7c3339ec03"), rootSize: 43136 },
{ _id: ObjectId("5ef7daa6ae8659149c976582"), rootSize: 40823 },
{ _id: ObjectId("5f3495129861ce45eb4e9728"), rootSize: 394884 },
{ _id: ObjectId("5ef7d7f6ae8659149c97657c"), rootSize: 125309 },
{ _id: ObjectId("5ef7dad6ae8659149c976584"), rootSize: 127447 },
{ _id: ObjectId("5fa56ef26538cd3bddd8389e"), rootSize: 17670 },
{ _id: ObjectId("5fa56ef26538cd3bddd8389f"), rootSize: 11398 },
{ _id: ObjectId("5fa56ef16538cd3bddd8389c"), rootSize: 2415 },
{ _id: ObjectId("5fa56ef36538cd3bddd838ae"), rootSize: 1757 },
{ _id: ObjectId("5fa56ef36538cd3bddd838b0"), rootSize: 4866 },
{ _id: ObjectId("5fa56ef36538cd3bddd838a8"), rootSize: 1510 },
{ _id: ObjectId("5fa56ef26538cd3bddd838a7"), rootSize: 39631 },
{ _id: ObjectId("5fa56ef36538cd3bddd838ab"), rootSize: 3662 },
{ _id: ObjectId("5fa56ef36538cd3bddd838aa"), rootSize: 15844 },
{ _id: ObjectId("5fa56ef16538cd3bddd8389d"), rootSize: 17196 },
{ _id: ObjectId("5fa56ef26538cd3bddd838a3"), rootSize: 34940 },
{ _id: ObjectId("5fa56ef36538cd3bddd838af"), rootSize: 468367 }
Which is great but it does not tell me how many elements are in the array/linear string within geometry.coordinates.
I have tried the following, but no cigar:
db.getCollection("_collectionName").aggregate([{$project: { count: { $size: { "$ifNull": [ "$geometry", [] ] } } } }])
MongoServerError: The argument to $size must be an array, but was of type: object
It comes back with an error, which i understand - so i referenced the coordinates array:
db.getCollection("_collectionName").aggregate([{$project: { count: { $size: { "$ifNull": [ "$geometry.coordinates", [] ] } } } }])
Which, returned the following data, again correct, if you understand GeoJSON files this is normal as this is the top level of the linear ring, sample data:
{ _id: ObjectId("5ef7da26ae8659149c97657e"), count: 1 }
{ _id: ObjectId("5ef7da45ae8659149c97657f"), count: 1 }
{ _id: ObjectId("5ef7daf1ae8659149c976585"), count: 1 }
{ _id: ObjectId("5f216685dbef0f7c3339ec03"), count: 1 }
So I then added the top level array of 0 to my aggregate function:
db.getCollection("_collectionName").aggregate([{$project: { count: { $size: { "$ifNull": [ "$geometry.coordinates.0", [] ] } } } }])
And this is what was returned:
{ _id: ObjectId("5ef7da26ae8659149c97657e"), count: 0 }
{ _id: ObjectId("5ef7da45ae8659149c97657f"), count: 0 }
{ _id: ObjectId("5ef7daf1ae8659149c976585"), count: 0 }
{ _id: ObjectId("5f216685dbef0f7c3339ec03"), count: 0 }
And that is not possible, here is a screenshot from Studio3T software:
Anybody who might be able to help or point me in the right direction please do so....
(I would be very grateful!)
The dot notation won't work on array elements within an aggregation. You'll want to use the $arrayElemAt operator, as follows:
db.getCollection("_aGStbl").aggregate([{
$project: {
count: { $size: { $arrayElemAt: [ "$geometry.coordinates", 0 ]}}
}
}])
To cater for Null values, you can use a $cond, depending on your objective for the output:
INSERT SOME DATA INTO A TESTDB:
db.arrayTest.insertMany([
{ _id: 1, arrayOfArrays: [ [ 1, 2, 3 ], [ 1, 2, 3, 4 ], [ 1, 2, 3, 4, 5, 6, 7 ] ] },
{ _id: 2, arrayOfArrays: [ [ 4, 5 ], [ 5, 6, 7 ] ] },
{ _id: 3, arrayOfArrays: [ [], [] ] },
{ _id: 4, arrayOfArrays: [ [], [], [] ] },
{ _id: 5 }
] )
{ acknowledged: true,
insertedIds: { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5 } }
TRY THESE AGGREGATE CALLS:
db.arrayTest.aggregate([{$project: { count: { $size: { "$ifNull": [ { $arrayElemAt: [ "$arrayOfArrays", 0 ] }, [ ] ] } } } } ] )
{ _id: 1, count: 3 }
{ _id: 2, count: 2 }
{ _id: 3, count: 0 }
{ _id: 4, count: 0 }
{ _id: 5, count: 0 }
db.arrayTest.aggregate([{$project: { count: { $cond: { if: {$arrayElemAt: [ "$arrayOfArrays", 0 ]}, then: { $size: { $arrayElemAt: [ "$arrayOfArrays", 0 ] } }, else: null} } } } ] )
{ _id: 1, count: 3 }
{ _id: 2, count: 2 }
{ _id: 3, count: 0 }
{ _id: 4, count: 0 }
{ _id: 5, count: null }

Mongoose In ArrayElemAt Issue In Aggregate

temporaryshifts is equal to
[{_id:123,
arr:[{_id:123321,name:"Bluh Bluh",date:"bluh bluh"}]
}]
so i want to access temporaryshifts[0].arr[0]
but i dont know how to access
$project:{
shiftArr:{$arrayElemAt:['$temporaryshifts',0]}
}
You have to make use of the let operators to make use of two $arrayElemAt Operators.
db.collection.aggregate([
{
"$project": {
"temporaryshifts": {
"$let": {
"vars": {
"masterKey": {
"$arrayElemAt": [
"$temporaryshifts",
0
]
}
},
"in": {
"$arrayElemAt": [
"$$masterKey.arr",
0
]
}
},
}
},
},
])
Mongo Playground Sample Execution
To Understand Properly i Am taking this example and suppose that this our data
`
[
{
_id: 123,
arr: [
{
_id: 123321,
name: "Bluh Bluh",
date: "bluh bluh"
},
{
_id: 1233219,
name: "Bluh Bluh2",
date: "bluh bluh2"
}
]
},
{
_id: 1234,
arr: [
{
_id: 1233214,
name: "Bluh Bluh4",
date: "bluh bluh4"
}
]
},
]
`
Can get temporaryshifts[0].arr[0] with this query
db.collection.aggregate([
{
"$match": {
"_id": 123
}
},
{
"$project": {
shiftArr: {
$arrayElemAt: [
"$arr",
0
]
}
}
}
])
You need to match at the first time to get the document and rest of thing arrayElementAt can manage.

Include array index after a filter in a nested array in mongodb

I have a collection containing objects like this one:
{
_id: '1234',
lines: [{
timestamp: 100,
data: {
prop1: 123,
prop2: 123,
prop3: 123
}
}, {
timestamp: 200,
data: {
prop1: 123,
prop2: 123,
prop3: 123
}
}, {
timestamp: 300,
data: {
prop1: 123,
prop2: 123,
prop3: 123
}
}]
}
I need to create a query to filter the lines in my object but I need to know what is the index of the object in the array. My query is like this:
collection.aggregateOne([
{ $match: { _id: queryId } },
{ $filter: { input: '$lines', as: 'line', cond: { $gt: ['$$line.timestamp', queryTimestamp ] } } }
]);
The query works, but I don't know what to write to have the array index of the lines in my output.
For example, if I'm looking for lines with a timestamp greater than 190 in my object, I want this output :
{
_id: '1234',
lines: [{
index: 1, // Index is 1 because the first element is filtered
timestamp: 200,
data: {
prop1: 123,
prop2: 123,
prop3: 123
}
}, {
index: 2,
timestamp: 300,
data: {
prop1: 123,
prop2: 123,
prop3: 123
}
}]
}
Is there a way to achieve that? I am using Mongodb 4.4.1.
This aggregation query returns the desired output:
db.collection.aggregate([
{
$addFields: {
lines: {
$reduce: {
input: { $range: [ 0, { $size: "$lines" } ] },
initialValue: [ ],
in: {
$concatArrays: [
"$$value",
{
$let: {
vars: {
doc: { $arrayElemAt: [ "$lines", "$$this" ] }
},
in: {
$cond: {
if: { $gt: ['$$doc.timestamp', queryTimestamp ] },
then: [ { $mergeObjects: [ "$$doc", { index: "$$this"} ] }],
else: [ ]
}
}
}
}
]
}
}
}
}
}
])
If I understood the question correctly, author wants to add index in sequence for the filtered data, where the filter is timestamp > 190.
While the accepted answer seems to be correct w.r.t the data posted in the question, it wont work for below data:
{
"_id" : "1234",
"lines" : [
{
"timestamp" : 100,
"data" : {
"prop1" : 123,
"prop2" : 123,
"prop3" : 123
}
},
{
"timestamp" : 200,
"data" : {
"prop1" : 123,
"prop2" : 123,
"prop3" : 123
}
},
{
"timestamp" : 180, // <- 180 in between 200 and 300 will break the sequence
"data" : {
"prop1" : 123,
"prop2" : 123,
"prop3" : 123
}
},
{
"timestamp" : 300,
"data" : {
"prop1" : 123,
"prop2" : 123,
"prop3" : 123
}
}
]
}
Output based on accepted answer:
{
"_id" : "1234",
"lines" : [
{
"timestamp" : 200,
"data" : {
"prop1" : 123,
"prop2" : 123,
"prop3" : 123
},
"index" : 1
},
{
"timestamp" : 300,
"data" : {
"prop1" : 123,
"prop2" : 123,
"prop3" : 123
},
"index" : 3
}
]
}
So improving the answer by #prasad_:
const queryTimestamp = 190;
db.collectionName.aggregate([
{ $unwind: "$lines" },
{
$match: {
"lines.timestamp": { $gt: queryTimestamp }
}
},
{
$group: {
_id: "$_id",
"lines": { $push: "$lines" }
}
},
{
$addFields: {
"lines": {
$reduce: {
input: { $range: [0, { $size: "$lines" }] },
initialValue: [],
in: {
$concatArrays: [
"$$value",
[
{
timestamp: { $arrayElemAt: ["$lines.timestamp", "$$this"] },
data: { $arrayElemAt: ["$lines.data", "$$this"] },
index: { $add: ["$$this", 1] }
}
]
]
}
}
}
}
}
]);
Output based on improved answer:
{
"_id" : "1234",
"lines" : [
{
"timestamp" : 200,
"data" : {
"prop1" : 123,
"prop2" : 123,
"prop3" : 123
},
"index" : 1
},
{
"timestamp" : 300,
"data" : {
"prop1" : 123,
"prop2" : 123,
"prop3" : 123
},
"index" : 2
}
]
}

How to get result count role on behalf of created?

[
{
"_id": '1',
"role":'admin',
"created":"0",
},
{
"_id": '2',
"role":'vendor',
"created":"1",
},
{
"_id": '3',
"role":'cus',
"created":"1",
},
{
"_id": '4',
"role":'cus',
"created":"1",
},
{
"_id": '5',
"role":'admin1',
"created":"1",
},
{
"_id": '6',
"role":'support',
"created":"5",
},
{
"_id": '7',
"role":'support',
"created":"5",
},
]
Would like to this type of result
Thanks in Advance
[
{
_id: 1,
created: "admin",
counts: [
{
role: "vendor",
count: 1
},
{
role: "cus",
count: 2
}
]
},
{
_id: 5,
created: "admin1",
counts: [
{
role: "support",
count: 2
}
]
}
]
You can group by role and count created using this query.
db.<yourCollectionName>.aggregate ( { $group : { _id: "$role" , count: { $sum:1 } } })
Above query returns output like this...
{ "_id" : "cus", "count" : 2 }
Another variation of aggregation is..
db.counts.aggregate([{ $group: { _id: "$role", count: { $sum:1 }, Data: { $push: "$$ROOT" } } }]).pretty()
Above query returns output like this...
{
"_id" : "cus",
"count" : 2,
"Data" : [
{
"_id" : ObjectId("5db368e9d130deb5c19f35c8"),
"role" : "cus",
"created" : "1"
},
{
"_id" : ObjectId("5db36913d130deb5c19f35c9"),
"role" : "cus",
"created" : "1"
}
]
}

Resources