Project array elements as individual fields - mongodb

I have a collection with a field in each document that looks like:
{
person = [{"name":"Alex"},{"name":"Betsy"},{"name":"Chauncy"}]
}
Documents can have different length for this array
I'm trying to project the individual element of this array as fields within the same document with the key as Name {index} and the corresponding value for the name. I can't seem to generate the projected fields given an array.
Expected output:
[{
"name1": "Alex",
"name2": "Betsy",
"name3": "Chauncy"
}]

You can use aggregation
$unwind to deconstruct the array and it helps to get the index too
$group to reconstruct the array with key (k): value(v) pair which helps in next stage
$arrayToObject since group already made k:v pair, this helps to make array to opbect
$replaceRoot helps to make the object as root
Here is the code
db.collection.aggregate([
{
$unwind: { path: "$person", includeArrayIndex: "index" }
},
{
$group: {
_id: null,
person: {
$push: {
k: { "$concat": [ "name", { $toString: "$index" } ] },
v: "$person.name"
}
}
}
},
{
$project: {
person: { "$arrayToObject": "$person" }
}
},
{
"$replaceRoot": { "newRoot": "$person" }
}
])
Working Mongo playground

Related

How do I sort results based on a specific array item in MongoDB?

I have an array of documents that looks like this:
patient: {
conditions: [
{
columnToSortBy: "value",
type: "PRIMARY"
},
{
columnToSortBy: "anotherValue",
type: "SECONDARY"
},
]
}
I need to be able to $sort by columnToSortBy, but using the item in the array where type is equal to PRIMARY. PRIMARY is not guaranteed to be the first item in the array every time.
How do I set my $sort up to accommodate this? Is there something akin to:
// I know this is invalid. It's for illustration purposes
$sort: "columnToSortBy", {$where: {type: "PRIMARY"}}
Is it possible to sort a field, but only when another field matches a query? I do not want the secondary conditions to affect the sort in any way. I am sorting on that one specific element alone.
You need to use aggregation framework
db.collection.aggregate([
{
$unwind: "$patient.conditions" //reshape the data
},
{
"$sort": {
"patient.conditions.columnToSortBy": -1 //sort it
}
},
{
$group: {
"_id": "$_id",
"conditions": { //re group it
"$push": "$patient.conditions"
}
}
},
{
"$project": { //project it
"_id": 1,
"patient.conditions": "$conditions"
}
}
])
Playground

How to $push all of the document in aggregation instead of specific field

I have this array of results in the first stage of the aggregation pipeline, using a $match:
[
{ a: 1, b: 2 },
{ a: 3, b: 4 }
]
Now I want to sum all of the A's and B's and also still have them so I will have something like this as a result:
{
total_sum: 10,
items: [...] // first and second objects ofcourse
}
I have tried to $group and $push, however, push only pushes specific fields from the object and I need to name A and B, instead just of parsing all of them.
How can I do it?
$set - Create new field total to sum a and b.
$group - Group by null, indicate to sum all total and add the document in items array.
With $$ROOT it will add the whole document.
items: {
$push: "$ROOT"
}
If you want just some fields only, then specify the (desired) document pattern.
items: {
$push: {
A: "$a",
B: "$b"
}
}
$unset (If use $push $$ROOT) - Remove total field from items field.
db.collection.aggregate([
{
$set: {
total: {
$sum: [
"$a",
"$b"
]
}
}
},
{
$group: {
_id: null,
total_sum: {
$sum: "$total"
},
items: {
$push: "$$ROOT"
}
}
},
{
$unset: "items.total"
}
])
Sample Mongo Playground

can't get data for unknown key in mongodb query

I want to get a subarray from a document using mongodb queries but I don't know the key for it. My data looks something like this:
{ 'log file name I dont know': [Array] }
'log file name I dont know' is the unique key at that level. Is there something like "get value for the first key" in mongodb aggregation pipeline?
1 Maybe something like this:
db.collection.aggregate([
{
$group: {
_id: "$_id",
new: {
$first: "$$ROOT"
}
}
},
{
"$project": {
"map": {
"$objectToArray": "$new"
}
}
},
{
$unwind: "$map"
},
{
$match: {
"map.k": {
"$ne": "_id"
},
"map.v": 4
}
}
])
Explained:
Add the root document to new key named "new"
Convert the object named "new" to array named "map"
$unwind the array
Remove the array elements with k:"_id" , so in the final result to have only:
map.k -> the unknown keys
map.v -> the array values for the unknown keys
Finally you can add in the $match stage the searched element , in the example:"map.v":4
playground1
2 Here is the option when you want to preserve the strange structure with the unknown key after the match stage:
db.collection.aggregate([
{
$group: {
_id: "$_id",
new: {
$first: "$$ROOT"
}
}
},
{
"$project": {
"map": {
"$objectToArray": "$new"
}
}
},
{
$match: {
"map.v": 1
}
},
{
"$project": {
"map": {
"$arrayToObject": "$map"
}
}
},
{
$replaceRoot: {
newRoot: "$map"
}
}
])
Explained:
Add the root document to new key named "new"
Convert the object named "new" to array named "map"
Match the necessary documents(in the example this is array element 1 ( "map.v":1 )
Convert back the array to object.
Replace the root tocument with the "map docunent" so it looks as original collection
playground2
Few words: File name value is not recommended to be a key , best practice is the filename to be key/value so if you want to search on filename value to be possible to create index on that filename key field , so your json structure to looke something like:
{ "filename":"The unknown file name" , "theArray":[1,2,3] }

Count nested wildcard array mongodb query

I have the following data of users and model cars:
[
{
"user_id":"ebebc012-082c-4e7f-889c-755d2679bdab",
"car_1a58db0b-5449-4d2b-a773-ee055a1ab24d":1,
"car_37c04124-cb12-436c-902b-6120f4c51782":0,
"car_b78ddcd0-1136-4f45-8599-3ce8d937911f":1
},
{
"user_id":"f3eb2a61-5416-46ba-bab4-459fbdcc7e29",
"car_1a58db0b-5449-4d2b-a773-ee055a1ab24d":1,
"car_0d15eae9-9585-4f49-a416-46ff56cd3685":1
}
]
I want to see how many users have a car_ with the value 1 using mongodb, something like:
{"car_1a58db0b-5449-4d2b-a773-ee055a1ab24d": 2}
For this example.
The issue is that I will never know how are the fields car_ are going to be, they will have a random structure (wildcard).
Notes:
car_id and user_id are at the same level.
The car_id is not given, I simply want to know for the entire database which are the most commmon cars_ with value 1.
$group by _id and convert root object to array using $objectToArray,
$unwind deconstruct root array
$match filter root.v is 1
$group by root.k and get total count
db.collection.aggregate([
{
$group: {
_id: "$_id",
root: { $first: { $objectToArray: "$$ROOT" } }
}
},
{ $unwind: "$root" },
{ $match: { "root.v": 1 } },
{
$group: {
_id: "$root.k",
count: { $sum: 1 }
}
}
])
Playground

Is there a way to give order field to the result of MongoDB aggregation?

Is there any way to give order or rankings to MongoDB aggregation results?
My result is:
{
"score":100
"name": "John"
},
{
"score":80
"name": "Jane"
},
{
"score":60
"name": "Lee"
}
My wanted result is:
{
"score":100
"name": "John",
"rank": 1
},
{
"score":80
"name": "Jane"
"rank": 2
},
{
"score":60
"name": "Lee"
"rank": 3
}
I know there is a operator called $includeArrayIndex but this only works with $unwind operator.
Is there any way to give rank without using $unwind?
Using $unwind requires grouping on my collection, and I'm afraid grouping pipeline would be too huge to process.
The other way is to use $map and add rank in document using its index, and don't use $unwind stage because it would be single field array you can directly access using its key name as mention in last line of code,
$group by null and make array of documents in root array,
$map to iterate loop of root array, get the index of current object from root array using $indexOfArray and increment that returned index number using $add because index start from 0, and that is how we are creating rank field, merge object with current element object and rank field using $mergeObjects
let result = await db.collection.aggregate([
{
$group: {
_id: null,
root: {
$push: "$$ROOT"
}
}
},
{
$project: {
_id: 0,
root: {
$map: {
input: "$root",
in: {
$mergeObjects: [
"$$this",
{
rank: { $add: [{ $indexOfArray: ["$root", "$$this"] }, 1] }
}
]
}
}
}
}
}
]);
// you can access result using root key
let finalResult = result[0]['root'];
Playground