How to use `$size` with embedded document? - mongodb

I'm having difficulty getting the size of arrays within embedded documents.
Given a collection with some document:
{
"_id": { "$oid": "60e26137600b71f2939ebbe7" },
"name": "Outer doc",
"inner_docs": [
{ "name": "one", "vals": [1,2,3,4,5] },
{ "name": "two", "vals": [1,2,3,4] },
{ "name": "three", "vals": [1,2,3] }
]
}
By applying a $project stage in aggregation with $size like:
{
"outer_num": { "$size": "$inner_docs" },
"inner_docs.num_vals": { "$size": "$inner_docs.vals" }
}
I get the resultant doc:
{
"_id": { "$oid": "60e26137600b71f2939ebbe7" },
"inner_docs": [
{ "name": "one", "num_vals": 3 },
{ "name": "two", "num_vals": 3 },
{ "name": "three", "num_vals": 3 }
],
"outer_num": 3,
}
outer_num is correct, but num_vals is not, it should be the values 5, 4 and 3 respectively.
How could I fix this?

Demo - https://mongoplayground.net/p/dpsp907E_fa
Use $map
Applies an expression to each item in an array and returns an array with the applied results.
db.collection.aggregate([
{
$project: {
"outer_num": { $size: "$inner_docs"},
"inner_docs": {
$map: {
input: "$inner_docs",
as: "doc",
in: { name: "$$doc.name", num_vals: { $size: [ "$$doc.vals" ] } }
}
}
}
}
])

Related

$lookup as into array does not insert into each object of the array

I have source collection like:
[
{
"_id": "0xeAAB59269bD1bA8522E8e5E0FE510F7aa4d47A09",
"id": "0xeAAB59269bD1bA8522E8e5E0FE510F7aa4d47A09",
"gameItemsWithQty": [
{
"gameItem": { "_id": 1 },
"qty": 1000
},
{
"gameItem": { "_id": 2 },
"qty": 1000
},
{
"gameItem": { "_id": 3 },
"qty": 1000
},
{
"gameItem": { "_id": 4 },
"qty": 1000
}
]
},
]
and game items collection like:
[
{
"_id": 1,
"id": 1,
"name": "Stamina Refill"
},
{
"_id": 2,
"id": 2,
"name": "Large Stamina Refill"
},
{
"_id": 3,
"id": 3,
"name": "XL Large Stamina Refill"
},
{
"_id": 4,
"id": 4,
"name": "Large Stamina Refill"
}
]
When performing lookup from first onto second collection:
{
from: 'game-items',
localField: 'gameItemsWithQty.gameItem._id',
foreignField: '_id',
as: 'gameItemsWithQty.gameItem'
}
It nests gameItem array into gameItemsWithQty field.
"_id": ...,
"id": ...,
"gameItemsWithQty": {
"gameItem": {
0: ...,
1: ...,
2: ...,
3: ...
}
}
I need it to nest results into each respective gameItem object inside of each object in gameItemsWithQty.
"_id": ...,
"id": ...,
"gameItemsWithQty": {
0: {
gameItem: ...
qty: ...
},
1: {
gameItem: ...
qty: ...
},
...
}
How do I correct this aggregation pipeline to achieve that?
$lookup - Lookup and return the result as gameItems array.
$set - Set gameItemsWithQty field.
2.1. $map - Iterate the element in gameItemsWithQty array and returns new array.
2.1.1. $mergeObjects - Merge current iterate document with the result 2.1.2.
2.1.2. Document with gameItem field. Merge the result of 2.1.2.1 with the gameItem field of current iterate document.
2.1.2.1. Get the first document (via $first) from the filtered array by matching the _ids.
db.source.aggregate([
{
$lookup: {
from: "game-items",
localField: "gameItemsWithQty.gameItem._id",
foreignField: "_id",
as: "gameItems"
}
},
{
$set: {
gameItemsWithQty: {
$map: {
input: "$gameItemsWithQty",
as: "gameItemWithQty",
in: {
$mergeObjects: [
"$$gameItemWithQty",
{
gameItem: {
$mergeObjects: [
{
$first: {
$filter: {
input: "$gameItems",
cond: {
$eq: [
"$$gameItemWithQty.gameItem._id",
"$$this._id"
]
}
}
}
},
"$$gameItemWithQty.gameItem"
]
}
}
]
}
}
}
}
},
{
$unset: "gameItems"
}
])
Demo # Mongo Playground

mongodb - query to count number of array elements inside an object

I've a mongodb collection "customer_vehicle_details" as below:
{
"_id": ObjectId("5660c2a5b6fcba2d47baa2d9"),
"customer_id": 4,
"customer_vehicles": {
"cars": [
{
"id": 1,
"name": "abc"
},
{
"id": 2,
"name": "xyz"
}
],
"bikes": [
{
"id": 1,
"name": "pqr"
},
{
"id": 2,
"name": "asdf"
}
]
}
}
I want to count total number of "cars" and total number of "bikes" separately in "customer_vehicles" collections. Not a sum of cars and bikes.
I tried with
db.customer_vehicle_details.aggregate(
{
$group: {
_id: "$customer_vehicles.cars",
total: { $sum: { $size:"$cars" } }
}
}
)
but this is giving me an error ""errmsg" : "The argument to $size must be an array, but was of type: missing"
How do I count total number of array elements inside an object in mongoDB?
Does this helps:
db.collection.aggregate([
{
"$project": {
carsCount: {
"$size": "$customer_vehicles.cars"
},
bikesCount: {
"$size": "$customer_vehicles.bikes"
}
}
}
])
Here's the playground link.

Wildcard for key in mongodb query

I have a collection equivalent to:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"sides": {
"0": {
"dist": 100
},
"1": {
"dist": 10
}
}
},
{
"_id": ObjectId("5a934e000102030405000001"),
"sides": {
"0": {
"dist": 100
}
}
}
]
I would like to perform a query that return any documents that has for any key nested in sides has the key dist with a specific value. Something like:
db.collection.find({"sides.*.dist": 10})
Here * acts as a wildcard, any key would be valid in its place.
That would retrieve:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"sides": {
"0": {
"dist": 100
},
"1": {
"dist": 10
}
}
}
]
On the other hand
db.collection.find({"sides.*.dist": 100})
Would retrive both documents.
the following song and dance won't be neccessary if sides field was an array...
db.collection.find(
{
$expr: {
$gt: [{
$size: {
$filter: {
input: { $objectToArray: "$sides" },
as: "x",
cond: { $eq: ["$$x.v.dist", 10] }
}
}
}, 0]
}
})
You could get the matching elements using this
db.collection.aggregate([
{
"$project": {
"sides_array": {//Reshape the sides
"$objectToArray": "$sides"
}
}
},
{//Denormalize to get more than one matches
"$unwind": "$sides_array"
},
{
"$match": {//Condition goes here
"sides_array.v.dist": 10
}
},
{
"$group": {//Group the data back, after unwinding
"_id": "$_id",
"sides": {
"$push": "$sides_array"
}
}
},
{
"$project": {//Reshape the data
"_id": 1,
"sides": {
"$arrayToObject": "$sides"
}
}
}
])

Sort records by array field values in MongoDb

I have a collection which has documents like;
{
"name": "Subject1",
"attributes": [{
"_id": "security_level1",
"level": {
"value": "100",
"valueKey": "ABC"
}
}, {
"_id": "security_score1",
"level": {
"value": "1000",
"valueKey": "CDE"
}
}
]
},
{
"name": "Subject2",
"attributes": [{
"_id": "security_level1",
"level": {
"value": "99",
"valueKey": "XYZ"
}
}, {
"_id": "security_score1",
"level": {
"value": "2000",
"valueKey": "EDF"
}
}
]
},
......
Each document will have so many attributes generated dynamically, can be different in size.
Is it possible to sort records based on level.value of security_level1? (security_level1 is _id field value)
As per above example, the second document ("name": "Subject2") should come first as the value ('level.value') of _id:security_level1 is 99, which is less than of Subject1's security_level1 value (100) - (Ascending order)
Use $filter and $arrayElemAt to get security_level1 item. Then you can use $toInt to convert that value to an integer so that $sort can be applied:
db.collection.aggregate([
{
$addFields: {
level: {
$let: {
vars: {
level_1: { $arrayElemAt: [ { $filter: { input: "$attributes", cond: { $eq: [ "$$this._id", "security_level1" ] } } } ,0] }
},
in: {
$toInt: "$$level_1.level.value"
}
}
}
}
},
{
$sort: {
level: 1
}
}
])
Mongo Playground

Aggregation on complex objects

I have a collection with documents like the following:
{
"towers": [
{
"name": "foo",
"towers": [
{
"name": "A",
"buildType": "Apartament"
},
{
"name": "B",
"buildType": "Apartament"
}
]
},
{
"name": "xpto",
"towers": [
{
"name": "C",
"buildType": "House"
},
{
"name": "D",
"buildType": "Office"
}
]
}
]
}
All I need to know is what are all the possible values for "buildType", like:
Apartment
House
Office
It's a complex object and the data to aggregate is deep inside it. Is there any way to achieve the results I want?
You need to $unwind the two nested array that is "towers" and "towers.towers" and then use $group with "towers.towers.buildType" field to get the distinct values
db.collection.aggregate([
{ "$unwind": "$towers" },
{ "$unwind": "$towers.towers" },
{ "$group": {
"_id": "$towers.towers.buildType"
}}
])
Output
[
{
"_id": "Office"
},
{
"_id": "House"
},
{
"_id": "Apartament"
}
]
db.collection.aggregate(
// Pipeline
[
// Stage 1
{
$unwind: {
path: "$towers",
}
},
// Stage 2
{
$unwind: {
path: "$towers.towers",
}
},
// Stage 3
{
$group: {
_id: '$_id',
buildType: {
$addToSet: '$towers.towers.buildType'
}
}
},
]
);