Map in Mongodb with aggregation - node.js

Here is my Data in DB
{
name: abc
pro: 1
},
{
name:cde,
pro: 2
},
{
name:fgh,
pro:3
},
{
name:ijk,
pro:4
},
here is my query to aggregate the result I've successfully get the count of name and pro:
db.aggregate([
{
$facet: {
"name": [
{ $group: { _id: '$name', N: { $sum: 1 } } }
],
},{ $project: { "pro": {
$map: {
input: '$pro',
in:{ $arrayToObject: [[{ k: '$$this._id', v: '$$this.N'}]] }
}
},
}}])
For pro which is 1 in DB but against 1, I want to map a etc, in response.
Expecting Output:
{
name: abc
pro: a //value in DB is 1.
},
{
name:cde,
pro: b //value in DB is 2.
},
{
name:fgh,
pro:c //value in DB is 3
},
{
name:ijk,
pro:d //value in DB is 4
},
}

If I understand your question correctly, you need to map pro integer values to corresponding characters. Then this should work:
db.collection.aggregate([
{
$project: {
_id: 0,
name: 1,
pro: {
$slice: [
[
"",
"a",
"b",
"c",
"d"
],
"$pro",
1
]
}
}
},
{
"$unwind": "$pro"
}
])
Here is the playground link.

Use Map pro function, it will works
db.collection.aggregate([
{
$project: {
_id: 0,
name: 1,
pro: {
$slice: [
[
"",
"a",
"b",
"c",
"d"
],
"$pro",
1
]
}
}
},
{
"$unwind": "$pro"
}
])

Related

Mongodb search field with range inside array of object

I have multiple documents in a collection like this
[
{
_id: 123,
data: 1,
details: [
{
item: "a",
day: 1
},
{
item: "a",
day: 2
},
{
item: "a",
day: 3
},
{
item: "a",
day: 4
}
],
someMoreField: "xyz"
}
]
Now I want document with _id: 123 and details field should only contain day within range of 1 to 3. So the result will be like below.
{
_id: 123,
data: 1,
details: [
{
item: 'a',
day: 1,
},
{
item: 'a',
day: 2,
},
{
item: 'a',
day: 3,
},
],
someMoreField: 'xyz',
};
I tried to do this by aggregate query as:
db.collectionaggregate([
{
$match: {
_id: id,
'details.day': { $gt: 1, $lte: 3 },
},
},
{
$project: {
_id: 1,
details: {
$filter: {
input: '$details',
as: 'value',
cond: {
$and: [
{ $gt: ['$$value.date', 1] },
{ $lt: ['$$value.date', 3] },
],
},
},
},
},
},
])
But this gives me empty result. Could someone please guide me through this?
You are very close, you just need to change the $gt to $gte and $lt to $lte.
Another minor syntax error is you're accessing $$value.date but the schema you provided does not have that field, it seems you need to change it to $$value.day, like so:
db.collection.aggregate([
{
$match: {
_id: 123,
"details.day": {
$gt: 1,
$lte: 3
}
}
},
{
$project: {
_id: 1,
details: {
$filter: {
input: "$details",
as: "value",
cond: {
$and: [
{
$gte: [
"$$value.day",
1
]
},
{
$lte: [
"$$value.day",
3
]
},
],
},
},
},
},
},
])
Mongo Playground

How to find the latest date in nested array of objects (MongoDB)

I am trying to find the latest "order" in "orders" array in the whole collection (Not only in the one object).
Data:
[
{
_id: 1,
orders: [
{
title: 'Burger',
date: {
$date: '2021-07-18T13:12:08.717Z',
},
},
],
},
{
_id: 2,
orders: [
{
title: 'Salad',
date: {
$date: '2021-07-18T13:35:01.586Z',
},
},
],
},
];
Code:
var restaurant = await Restaurant.findOne({
'orders.date': 1,
});
Rather simple:
db.collection.aggregate([
{ $project: { latest_order: { $max: "$orders.date" } } }
])
If you like to get the full order use this:
db.collection.aggregate([
{
$project: {
latest_order: {
$first: {
$filter: {
input: "$orders",
cond: { $eq: [ "$$this.date", { $max: "$orders.date" } ] }
}
}
}
}
},
{ $sort: { "latest_order.date": 1 } },
{ $limit: 1 }
])
Mongo Playground
You have to use aggregation for that
db.collection.aggregate([
{ $unwind: "$orders" },
{ $sort: { "orders.date": -1 } },
{ $limit: 1 },
{
"$group": {
"_id": "$_id",
"orders": { "$first": "$orders" }
}
}
])
Working Mongo playground

mongoose match multiple ids and count if duplicate found

I have a list of ids and it may contain duplicates, so ignore duplicates and count the occurrence of total duplicates. I will explain it in details.
IDS
[
5fe10a8c4d6b0fb7f70bbf84,
5fe10a8c4d6b0fb7f70bbf84,
5ff2aad439a8602fd872ab7c
]
I have used the below code to get the result,
var user_id = [
5fe10a8c4d6b0fb7f70bbf84,
5fe10a8c4d6b0fb7f70bbf84,
5ff2aad439a8602fd872ab7c
];
User.aggregate([
{
$match: {
_id: {
$in: user_id
}
},
},
{
$group: {
_id: "$_id",
count: {
$sum: 1
}
}
},
],
(err, resp) => {
console.log(resp)
})
The output for above code is,
[
{ _id: 5ff2aad439a8602fd872ab7c, count: 1 },
{ _id: 5fe10a8c4d6b0fb7f70bbf84, count: 1 }
]
The required output is,
[
{ _id: 5ff2aad439a8602fd872ab7c, count: 1 },
{ _id: 5fe10a8c4d6b0fb7f70bbf84, count: 2 }
]
I have tried many code but no success, is there anyway to achieve required output.
you trying to get the count from _id. _id is unique for all the documents so you need to change the _id to some other fields which is not unique.
example,
table
[
{
id: 1,
values: [
1,
2,
3
]
},
{
id: 1,
values: [
1,
2,
3
]
},
{
id: 1,
values: [
1,
2,
3
]
},
{
id: 3,
values: [
1,
2,
3
]
}
]
Query
db.collection.aggregate([
{
$match: {
id: {
$in: [
1,
3
]
}
},
},
{
$group: {
_id: "$id",
count: {
$sum: 1
}
}
},
])
output
[
{
"_id": 1,
"count": 3
},
{
"_id": 3,
"count": 1
}
]

How to shift element from one array position to another in MongoDB with mongoose?

I have got an array of objects in MongoDB and I was moving a particular element id (i.e 1) from its position to below element having id (i.e 2). So that we can get element with id as 2 -> 1 -> 3.
const arr = [
{
id: 1,
name: 'foo'
},
{
id: 2,
name: 'bar'
},
{
id: 3,
name: 'zoo'
}
]
What I've done is used $pull and $push but it gives ConflictingUpdateOperators and I don't know how to deal with it.
updatedPlan = await Plan.findOneAndUpdate(
{ _id: req.params.id },
{
$pull: {
"arr": {
id: 1
}
},
$push: {
"arr" : {
$each: [{ id: 1, name: 'foo'}],
$position: 1
}
},
);
In MongoDB 4.2 or newer you can update a document with Aggregation Pipeline. Using simple $map on a $range of array indexes you can shuffle these indexes and use $arrayElemAt in order to build a new array:
db.col.update({ _id: req.params.id }, [
{
$set: {
arr: {
$map: {
input: { $range: [ 0, { $size: "$arr" } ] },
in: {
$let: {
vars: {
newIndex: {
$switch: {
branches: [
{ case: { "$eq": [ "$$this", 0 ] }, then: 1 },
{ case: { "$lte": [ "$$this", 1 ] }, then: { $subtract: [ "$$this", 1 ] } },
],
default: "$$this"
}
}
},
in: {
$arrayElemAt: [ "$arr", "$$newIndex" ]
}
}
}
}
}
}
}
])

Merge $lookup result into existing document array

-room collection
_id: ObjectId("xxx")
bedspaces: Array
0:ObjectId("xx")
1:ObjectId("xx")
***
***
-bedspace collection
_id: ObjectId("xxxx");
number: 1
decks: Array
{
_id: ObjectId("xxx");
number: 1
status: "Vacant"
tenant: ObjectId("5c964ae7f5097e3020d1926c")
dueRent: 11
away: null
},
{
_id: ObjectId("xxx");
number: 2
status: "Vacant"
tenant: null
dueRent: 11
away: null
}
Under the decks array, is my tenant field, that have objectId, and i am going to lookup this object id, in the tenants, collection.
-tenant collection
_id: ObjectId("5c964ae7f5097e3020d1926c");
name: 'John Doe'
-expected output
/*room collection*/
_id: ObjectId("xxx")
bedspaces: [
{
_id: ObjectId("xxx")
number: 1
decks: [
{
_id: ObjectId("xxx")
number: 1
status: "Vacant"
tenant: {
name: 'John Doe'
}
dueRent: 11
away: null
},
{
_id: ObjectId("xxx");
number: 1
status: "Vacant"
tenant: null
dueRent: 11
away: null
}
]
}
]
There is also an instances, that deck array is equal to null.
In below aggregation it will only display the decks, that have tenant with object id, what i want is to display both the decks.
{
from: 'beds',
let: {bedspace: '$bedspaces'},
pipeline:[
{
$match: {
$expr: {
$in: ["$_id", "$$bedspace"]
}
}
},
{
$unwind: "$decks"
},
{
$lookup: {
from: 'tenants',
let: {tenant: "$decks.tenant"},
pipeline: [
{
$match: {
$expr: {
$eq: ["$_id", "$$tenant"]
}
}
}
],
as: "decks.tenant",
}
},
{
$unwind: "$decks.tenant"
},
{ $group: {
_id: "$_id",
decks: { $push: "$decks" },
number: {$first: "$number"}
}}
],
as: "bedspaces"
}
"how can i add condition on my second look up, to execute only if tenant is not null", so that i could retrieve both decks, or any work-around so i could achieved my desired result
Don't really have time for all the explanation right now (sorry),
Explanation
The basic issue here is that usage of $unwind is your problem and you don't need it. Use $map on the produced array content merging with the "decks" array instead. Then you can have nulls.
What you want to do here is have the values from the $lookup from your "tenants" collection transposed into the existing array within your "beds/bedspaces" collection for it's own existing "tenant" values which are the ObjectId references for the foreign collection.
The $lookup stage cannot do this by simply naming the field path within the "as" output where that path is already inside another array, and in fact the output of $lookup is always an array of results obtained from the foreign collection. You want singular values for each actual match, and of course you expect a null to be in place where nothing matches, and of course keeping the original document array of "decks" intact, but just including the foreign details where those were found.
Your code attempt seems partially aware of this point as you are using $unwind on the $lookup result on the ""tenants" collection into a "temporary array" ( but you put in in the existing path and that overwrites content ) and then attempting to "re-group" as an array through $group and $push. But the problem of course is the $lookup result does not apply to every array member within "decks", so you end up with less results than you want.
The real solution is not a "conditional $lookup", but instead to transpose the "temporary array" content from the result into the existing "decks" entries. You do this using $map to process the array members, and $arrayElemAt along with $indexOfArray in order to return the matching elements from the "temporary array" by the matching _id values to "tenant".
{ "$lookup": {
"from": Tenant.collection.name,
"let": { "tenant": "$decks.tenant" },
"pipeline": [
{ "$match": {
"$expr": { "$in": [ "$_id", "$$tenant" ] }
}}
],
"as": "tenant"
}},
{ "$addFields": {
"decks": {
"$map": {
"input": "$decks",
"in": {
"$mergeObjects": [
"$$this",
{
"tenant": {
"$cond": {
"if": {
"$eq": [
{ "$indexOfArray": ["$tenant._id", "$$this.tenant"] },
-1
]
},
"then": null,
"else": {
"$arrayElemAt": [
"$tenant",
{ "$indexOfArray": ["$tenant._id", "$$this.tenant"]}
]
}
}
}
}
Noting there we are using $mergeObjects inside the $map in order to keep the existing content of the "decks" array and only replace ( or "merge" ) an overwritten representation of "tenant" for each array member. You are using the expressive $lookup already and this like $mergeObjects is a MongoDB 3.6 feature.
Just for interest the same thing can be done by just specifying every field within the array. i.e:
"decks": {
"$map": {
"input": "$decks",
"in": {
"_id": "$$this._id",
"number": "$$this.number",
"tenant": {
// same expression
},
"__v": "$$this.__v" // just because it's mongoose
}
}
}
Much the same can be said for the $$REMOVE used in the $addFields which is also another MongoDB 3.6 feature. You can alternately just use $project and simply omit the unwanted fields:
{ "$project": {
"number": "$number",
"decks": {
"$map": { /* same expression */ }
},
"__v": "$__v"
// note we don't use the "tenant" temporary array
}}
But that's basically how it works. By taking the $lookup result and then transposing those results back into the original array within the document.
Example Listing
Also abstracting on your data from previous questions here, which is a bit better than what you posted in the question here. Runnable listing for demonstration:
const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');
const uri = 'mongodb://localhost:27017/hotel';
const opts = { useNewUrlParser: true };
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndexes', true);
mongoose.set('debug', true);
const tenantSchema = new Schema({
name: String,
age: Number
});
const deckSchema = new Schema({
number: Number,
tenant: { type: Schema.Types.ObjectId, ref: 'Tenant' }
});
const bedSchema = new Schema({
number: Number,
decks: [deckSchema]
});
const roomSchema = new Schema({
bedspaces: [{ type: Schema.Types.ObjectId, ref: 'Bed' }]
});
const Tenant = mongoose.model('Tenant', tenantSchema);
const Bed = mongoose.model('Bed', bedSchema);
const Room = mongoose.model('Room', roomSchema);
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri, opts);
// Clean data
await Promise.all(
Object.entries(conn.models).map(([k, m]) => m.deleteMany())
);
// Insert data
let [john, jane, bilbo ] = await Tenant.insertMany([
{
_id: ObjectId("5c964ae7f5097e3020d1926c"),
name: "john doe",
age: 11
},
{
_id: ObjectId("5c964b2531bc162fdce64f15"),
name: "jane doe",
age: 12
},
{
_id: ObjectId("5caa5454494558d863513b24"),
name: "bilbo",
age: 111
}
]);
let bedspaces = await Bed.insertMany([
{
_id: ObjectId("5c98d89c6bd5fc26a4c2851b"),
number: 1,
decks: [
{
number: 1,
tenant: john
},
{
number: 1,
tenant: jane
}
]
},
{
_id: ObjectId("5c98d89f6bd5fc26a4c28522"),
number: 2,
decks: [
{
number: 2,
tenant: bilbo
},
{
number: 3
}
]
}
]);
await Room.create({ bedspaces });
// Aggregate
let results = await Room.aggregate([
{ "$lookup": {
"from": Bed.collection.name,
"let": { "bedspaces": "$bedspaces" },
"pipeline": [
{ "$match": {
"$expr": { "$in": [ "$_id", "$$bedspaces" ] }
}},
{ "$lookup": {
"from": Tenant.collection.name,
"let": { "tenant": "$decks.tenant" },
"pipeline": [
{ "$match": {
"$expr": { "$in": [ "$_id", "$$tenant" ] }
}}
],
"as": "tenant"
}},
{ "$addFields": {
"decks": {
"$map": {
"input": "$decks",
"in": {
"$mergeObjects": [
"$$this",
{
"tenant": {
"$cond": {
"if": {
"$eq": [
{ "$indexOfArray": ["$tenant._id", "$$this.tenant"] },
-1
]
},
"then": null,
"else": {
"$arrayElemAt": [
"$tenant",
{ "$indexOfArray": ["$tenant._id", "$$this.tenant"]}
]
}
}
}
}
]
}
}
},
"tenant": "$$REMOVE"
}}
],
"as": "bedspaces"
}}
]);
log(results);
} catch (e) {
console.error(e)
} finally {
mongoose.disconnect();
}
})()
Returns:
Mongoose: tenants.deleteMany({}, {})
Mongoose: beds.deleteMany({}, {})
Mongoose: rooms.deleteMany({}, {})
Mongoose: tenants.insertMany([ { _id: 5c964ae7f5097e3020d1926c, name: 'john doe', age: 11, __v: 0 }, { _id: 5c964b2531bc162fdce64f15, name: 'jane doe', age: 12, __v: 0 }, { _id: 5caa5454494558d863513b24, name: 'bilbo', age: 111, __v: 0 } ], {})
Mongoose: beds.insertMany([ { _id: 5c98d89c6bd5fc26a4c2851b, number: 1, decks: [ { _id: 5caa5af6ed3dce1c3ed72cef, number: 1, tenant: 5c964ae7f5097e3020d1926c }, { _id: 5caa5af6ed3dce1c3ed72cee, number: 1, tenant: 5c964b2531bc162fdce64f15 } ], __v: 0 }, { _id: 5c98d89f6bd5fc26a4c28522, number: 2, decks: [ { _id: 5caa5af6ed3dce1c3ed72cf2, number: 2, tenant: 5caa5454494558d863513b24 }, { _id: 5caa5af6ed3dce1c3ed72cf1, number: 3 } ], __v: 0 } ], {})
Mongoose: rooms.insertOne({ bedspaces: [ ObjectId("5c98d89c6bd5fc26a4c2851b"), ObjectId("5c98d89f6bd5fc26a4c28522") ], _id: ObjectId("5caa5af6ed3dce1c3ed72cf3"), __v: 0 })
Mongoose: rooms.aggregate([ { '$lookup': { from: 'beds', let: { bedspaces: '$bedspaces' }, pipeline: [ { '$match': { '$expr': { '$in': [ '$_id', '$$bedspaces' ] } } }, { '$lookup': { from: 'tenants', let: { tenant: '$decks.tenant' }, pipeline: [ { '$match': { '$expr': { '$in': [ '$_id', '$$tenant' ] } } } ], as: 'tenant' } }, { '$addFields': { decks: { '$map': { input: '$decks', in: { '$mergeObjects': [ '$$this', { tenant: [Object] } ] } } }, tenant: '$$REMOVE' } } ], as: 'bedspaces' } } ], {})
[
{
"_id": "5caa5af6ed3dce1c3ed72cf3",
"bedspaces": [
{
"_id": "5c98d89c6bd5fc26a4c2851b",
"number": 1,
"decks": [
{
"_id": "5caa5af6ed3dce1c3ed72cef",
"number": 1,
"tenant": {
"_id": "5c964ae7f5097e3020d1926c",
"name": "john doe",
"age": 11,
"__v": 0
}
},
{
"_id": "5caa5af6ed3dce1c3ed72cee",
"number": 1,
"tenant": {
"_id": "5c964b2531bc162fdce64f15",
"name": "jane doe",
"age": 12,
"__v": 0
}
}
],
"__v": 0
},
{
"_id": "5c98d89f6bd5fc26a4c28522",
"number": 2,
"decks": [
{
"_id": "5caa5af6ed3dce1c3ed72cf2",
"number": 2,
"tenant": {
"_id": "5caa5454494558d863513b24",
"name": "bilbo",
"age": 111,
"__v": 0
}
},
{
"_id": "5caa5af6ed3dce1c3ed72cf1",
"number": 3,
"tenant": null
}
],
"__v": 0
}
],
"__v": 0
}
]
Shows the null on the second entry of the second entry in the bedspaces array as expected.

Resources