Find documents matching a condition depending on another document value - mongodb

My collection contains the following documents:
{
"_id": "a",
"index": 1
},
{
"_id": "b",
"index": 2
},
{
"_id": "c",
"index": 3
},
{
"_id": "c",
"index": 4
}
Given an id, I would like to find all the documents having a greater index that the index corresponding to the id.
For example, if id="b", then index=2 and result would be
{
"_id": "c",
"index": 3
},
{
"_id": "c",
"index": 4
}
I thougt I could use an aggregation pipeline and use $add_field to add searched index into each document and the use a $match, but cannot find how to do it. I mean, my problem would be solved if I cold produce this result:
{
"_id": "a",
"index": 1,
"ref_index": 2
},
{
"_id": "b",
"index": 2,
"ref_index": 2
},
{
"_id": "c",
"index": 3,
"ref_index": 2
},
{
"_id": "c",
"index": 4,
"ref_index": 2
}

I am not sure is there any straight way to handle this operation, you need to do 2 queries or you can try below aggregation pipeline,
$facet to separate results, getIndex to get matching document of _id: "b", allDocs to get all documents
$filter to iterate loop of allDocs and filter document by index greater than condition
$unwind deconstruct allDocs array
$replaceRoot to replace allDocs object to root
db.collection.aggregate([
{
$facet: {
getIndex: [{ $match: { _id: "b" } }],
allDocs: [{ $match: {} }]
}
},
{
$project: {
allDocs: {
$filter: {
input: "$allDocs",
cond: {
$gt: [
"$$this.index",
{ $first: "$getIndex.index" }
]
}
}
}
}
},
{ $unwind: "$allDocs" },
{ $replaceRoot: { newRoot: "$allDocs" } }
])
Playground

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 querying for all min values that match criteria and indexing

Suppose I had the following:
[
{
"team": "A",
"age": 1,
"name": "Abe"
},
{
"team": "A",
"age": 5,
"name": "Apple"
},
{
"team": "B",
"age": 1,
"name": "Ben"
},
{
"team": "B",
"age": 2,
"name": "Bon"
},
{
"team": "C",
"age": 5,
"name": "Cherry"
}
]
I have the following query:
They must be in either TeamA or TeamB.
After filtering, I want only the youngest.
So in this example, it would return only Abe and Ben.
Preferably, I want it done in a single query. My guess is I have to use an aggregation pipeline, something like
db.People.aggregate(
[
$match: { $or: [{ team: 'A' }, { team: 'B' }] },
// some more stuff
);
Question1: I'm not sure what the next step would be. Could someone point me in the right direction?
Question2:
There may be a million records and I was thinking of adding two index:
Index on Team. I'm thinking this will allow it to filter Teams of interest.
Index on Age so that it can grab only the mins.
Would these indexes help or what kind of indexes should I be looking into?
**Edit: I'm getting closer, but I'm only interested in the records themselves. **
db.collection.aggregate([
{
$match: {
$or: [
{
team: "A"
},
{
team: "B"
}
]
}
},
{
$group: {
_id: "$age",
items: {
$push: "$$ROOT"
}
}
},
{
$sort: {
_id: 1
}
},
{
$limit: 1,
}
])

How to $sum a specific array inside an array?

Consider the following structure:
{
"groups": [
{
"group_id": "A",
"total": -1
"items": [
{
"item_id": "a",
"val": 1
},
{
"item_id": "b",
"val": 2
}
]
},
{
"group_id": "B",
"total": -1
"items": [
{
"item_id": "c",
"val": 3
},
{
"item_id": "d",
"val": 4
}
]
}
]
}
given a group_id, I'd like to $sum the items values of this group and set the total value with that number (e.g. the total of group A is 3)
How can I do that?
I know about $unwind but I don't fully understand how to do that only to the specific array I'm interested in
$map to iterate loop of groups array
$cond to check if group_id match then do sum operation otherwise nothing
$sum to get total of items.val
$mergeObjects to merge group object and new updated total field
db.collection.aggregate([
{
$set: {
groups: {
$map: {
input: "$groups",
in: {
$mergeObjects: [
"$$this",
{
$cond: [
{ $eq: ["$$this.group_id", "A"] },
{ total: { $sum: "$$this.items.val" } },
{}
]
}
]
}
}
}
}
}
])
Playground

How to add calculated fields inside subdocuments without using unwind?

I'm looking for a simple solution to add a field to a subdocument without using $unwind and $group.
I need only to calculate the sum and the size of a nested subdocuments and show it in a new field.
This is my starting collection:
{
"_id": ObjectId("5a934e000102030405000000"),
"subDoc": [
{
"a": 1,
"subArray": [1,2,3]
},
{
"b": 2
}
]
},
{
"_id": ObjectId("5a934e000102030405000001"),
"subDoc": [
{
"a": 1,
"subArray": [4,5,6]
},
{
"b": 2
},
{
"c": 3,
"subArray": [8,8,8]
}
]
}
And this is my desired result, where I've added sum (sum of subArray) and size (number of elements in subArray):
{
"_id": ObjectId("5a934e000102030405000000"),
"subDoc": [
{
"a": 1,
"subArray": [1,2,3],
"sum": 6,
"size": 3
},
{
"b": 2
"sum": 0,
"size": 0
}
]
},
{
"_id": ObjectId("5a934e000102030405000001"),
"subDoc": [
{
"a": 1,
"subArray": [4,5,6],
"sum": 15,
"size": 3
},
{
"b": 2,
"sum": 0,
"size": 0
},
{
"c": 3,
"subArray": [8,8],
"sum": 16,
"size": 2
}
]
}
I know how to obtain this result using $unwind and then $group, but I'd like to know if there is any other way (or a better way!) to achieve the same result. I've tried using $addFields and $map without success.
Working playground example: https://mongoplayground.net/p/fK8t6SLlOHa
$map to iterate loop of subDoc array
$sum to get total of subArray array of numbers
$ifNull to check if field is not present or null then return empty array because $size operator only allows array input
$size to get total elements in subArray array
$mergeObjects to merge current object with new added fields
db.collection.aggregate([
{
$addFields: {
subDoc: {
$map: {
input: "$subDoc",
in: {
$mergeObjects: [
"$$this",
{
sum: { $sum: "$$this.subArray" },
size: {
$size: { $ifNull: ["$$this.subArray", []] }
}
}
]
}
}
}
}
}
])
Playground

How to access the fields from arrays of a object in two different collections?

This is locations collection data.
{
_id: "1",
location: "loc1",
sublocations: [
{
_id: 2,
sublocation: "subloc1",
},
{
_id: 3,
sublocation: "subloc2",
}
]
},
{
_id: "4",
location: "loc2",
sublocations: [
{
_id: 5,
sublocation: "subloc1",
},
{
_id: 6,
sublocation: "subloc2",
}
]
}
This is products collection data
{
_id: "1",
product: "product1",
prices: [
{
_id: 2,
sublocationid: 2, //ObjectId of object in sublocations array
price: 500
},
{
_id: 3,
sublocationid: 5, //ObjectId of object in sublocations array
price: 200
}
]
}
Now I need to get the sublocation in product schema in the prices array. Expected result is as below.
{
_id: "1",
product: "product1",
prices: [
{
_id: 2,
sublocationid: 3,
sublocation: "subloc2",
price: 500
},
{
_id: 3,
sublocationid: 5,
sublocation: "subloc1"
price: 200
}
]
}
To achieve it, I did it like in the following way.
First, performing aggregation on locations collection - $unwind the sublocations array and store the $out in the new collection.
Second, perform aggregation on 'products' collection - $unwind the prices, $lookup the sublocationid from the new collection and $group them.
Third, after getting data delete the data of new collection.
Is there any other simplified way? Please let me know if there is any.
If you want to stick with 3.4 version, you can try this query:
db.products.aggregate([
{
$unwind: {
"path": "$prices"
}
},
{
$lookup: {
"from": "locations",
"localField": "prices.sublocationid",
"foreignField": "sublocations._id",
"as": "locations"
}
},
{
$unwind: {
"path": "$locations"
}
},
{
$unwind: {
"path": "$locations.sublocations"
}
},
{
$addFields: {
"keep": {
"$eq": [
"$prices.sublocationid",
"$locations.sublocations._id"
]
}
}
},
{
$match: {
"keep": true
}
},
{
$addFields: {
"price": {
"_id": "$prices._id",
"sublocationid": "$prices.sublocationid",
"sublocation": "$locations.sublocations.sublocation",
"price": "$prices.price"
}
}
},
{
$group: {
"_id": "$_id",
"product": { "$first": "$product" },
"prices": { "$addToSet": "$price" }
}
}
]);
It's not as nice as 3.6 version though, because of a higher memory consumption.
You can try below aggregation query in 3.6 version.
Since both local field and foreign field are array you have to $unwind both to do equality comparison.
For this you will have to use new $lookup syntax.
$match with $expr provides comparsion between document fields to look up the location's sublocation document for each product's sublocation id.
$project to project the matching sublocation doc.
$addFields with $arrayElemAt to convert the looked up sublocation array into a document.
$group to push all prices with matching sublocation's document for each product.
db.products.aggregate[
{
"$unwind": "$prices"
},
{
"$lookup": {
"from": "locations",
"let": {
"prices": "$prices"
},
"pipeline": [
{
"$unwind": "$sublocations"
},
{
"$match": {
"$expr": [
"$$prices.sublocationid",
"$sublocations._id"
]
}
},
{
"$project": {
"sublocations": 1,
"_id": 0
}
}
],
"as": "prices.sublocations"
}
},
{
"$addFields": {
"prices.sublocations": {
"$arrayElemAt": [
"$prices.sublocations",
0
]
}
}
},
{
"$group": {
"_id": "$_id",
"product": {
"$first": "$product"
},
"prices": {
"$push": "$prices"
}
}
}
])