Need desired outcome in populate block with customized result in mongodb - mongodb

I have four collections person , other_details, occupation_details, bank_details as stated below
person =>[
{
id : 1,
name : 'john',
other_details : 1023
},
{
id : 2,
name : 'mark',
other_details : 99
}
]
other_details => [
{
id: 1023,
married: false,
occupation_details: 144,
bank_details : 10
},
{
id: 99,
married: true,
occupation_details: 45,
bank_details : 11
}
]
occupation_details => [
{
id: 144,
comp_name : 'oscorp inc.'
},
{
id: 45,
comp_name : 'tesla inc.'
}
]
bank_details => [
{
id: 10,
bank : 'Bank of canada'
},
{
id: 11,
bank : 'Peoples bank of canada'
}
]
I am using mongoose library with nodejs
// id = 1
person.findById(id).populate({
path: 'other_details',
populate: [
{
path: 'occupation_details'
},
{
path: 'bank_details'
}
]
})
So the result for the above query comes like below
=>
{
id : 1,
name : 'john',
other_details : {
id: 1023,
married: false,
occupation_details: {
id: 144,
comp_name : 'oscorp inc.'
},
bank_details : {
id: 10,
bank : 'Bank of canada'
}
}
}
But for some reasons I want the result like below
{
id : 1,
name : 'john',
other_details : {
id: 1023,
married: false,
occupation_details: {
id: 144,
comp_name : 'oscorp inc.'
},
bank_details : 10,
custom_bank_details : {
id: 10,
bank : 'Bank of canada'
}
}
}
The change I need is the following, The bank_details object should exist with id and the populated bank_details data from other collection should come in other object name as custom_bank_details
bank_details : 10,
custom_bank_details : {
id: 10,
bank : 'Bank of canada'
}

Update:
You could use virtual to populate bank details into a new field. Something like
OtherDetailsSchema.virtual('custom_bank_details', {
ref: 'BankDetails',
localField: 'bank_details',
foreignField: '_id',
justOne: true
});
Person.findById(id).populate({
path: 'other_details',
populate: [
{path: 'occupation_details'},
{path: 'custom_bank_details'}
]})
Original
I'm not a mongoose user so I'm not sure if it is possible to populate to new field name. If feasible you could quite easily achieve using aggregate with lookup.
Something like
person.aggregate([
{"$lookup":{
"from":"other_details",
"let":{"other_details":"$other_details"},
"pipeline":[
{"$match":{"$expr":{"$eq":["$id","$$other_details"]}}},
{"$lookup":{
"from":"occupation_details",
"localField":"occupation_details",
"foreignField":"id",
"as":"occupation_details"
}},
{"$unwind":"$occupation_details"},
{"$lookup":{
"from":"bank_details",
"localField":"bank_details",
"foreignField":"id",
"as":"custom_bank_details"
}},
{"$unwind":"$custom_bank_details"},
],
"as":"other_details"
}},
{"$unwind":"$other_details"}
])
Working example https://mongoplayground.net/p/5Kee0tBQmTd

Related

MongoDB query - unwind and match preserving null OR different value/ add a new field based on a condition

If a have a following structure :
{
_id: 1,
name: 'a',
info: []
},
{
_id: 2,
name: 'b',
info: [
{
infoID: 100,
infoData: 'my info'
}
]
},
{
_id: 3,
name: 'c',
info: [
{
infoID: 200,
infoData: 'some info 200'
},
{
infoID: 300,
infoData: 'some info 300'
}
]
}
I need to query in such a way to obtain the documents where infoID is 100 showing the infoData, or nothing if info is empty, or contains subdocuments with infoID different from 100.
That is, I would want the following output:
{
_id: 1,
name: 'a',
infoData100: null
},
{
_id: 2,
name: 'b',
infoData100: 'my info'
},
{
_id: 3,
name: 'c',
infoData100: null
}
If I $unwind by info and $match by infoID: 100, I lose records 1 and 3.
Thanks for your responses.
Try below query :
Query :
db.collection.aggregate([
/** Adding a new field or you can use $project instead of addFields */
{
$addFields: {
infoData100: {
$cond: [
{
$in: [100, "$info.infoID"] // Check if any of objects 'info.infoID' has value 100
},
{
// If any of those has get that object & get infoData & assign it to 'infoData100' field
$let: {
vars: {
data: {
$arrayElemAt: [
{
$filter: {
input: "$info",
cond: { $eq: ["$$this.infoID", 100] }
}
},
0
]
}
},
in: "$$data.infoData"
}
},
null // If none has return NULL
]
}
}
}
]);
Test : MongoDB-Playground

Parent.save() not working when sub document / deeply nested document is modified

I find my document from whole collection like this:
const account = await Account.findOne({ "buildings.gateways.devices.verificationCode": code })
const buildings = account.buildings
const gateways = buildings[0].gateways;
const devices = gateways[0].devices;
const device = _.filter(devices, d => d.verificationCode === code);
now I want to change one of the property "patientLastName" and then save the whole document. I am doing as below.
device.patientLastName = lastName;
const updated = await account.save();
This simply does not change anything. I have tried many solutions given but none of them working.
not sure if I can save parent document just like that?
I have few other calls where same code works but only change for this is that this is in my post call while working ones are in put call.
My Schema:
const accountSchema = new mongoose.Schema({
email: { type: String, unique: true, required: true },
password: { type: String, required: true },
userName: { type: String, unique: true, required: true },
companyName: { type: String, required: true },
apiKey: { type: String, unique: true, required: true },
apiCallCount: { type: Number, default: 0 },
solutionType: String,
parentCompany: String,
buildings:
[
new mongoose.Schema({
buildingName: String,
address: String,
suite: String,
floor: String,
timeZone: String,
gateways:
[
new mongoose.Schema({
gatewayName: String,
gatewayKey: { type: String, sparse: true },
suite: String,
devices: [
new mongoose.Schema({
serialNumber: { type: String, sparse: true },
area: String,
connectionStatus: Number,
gatewayKey: String,
applicationNumber: Number,
firmwareVersion: String,
needsAttention: Boolean,
verificationCode: String,
patientRiskStatus: String,
patientFirstName: String,
patientLastName: String
}, { timestamps: true })
]
}, { timestamps: true })
]
}, { timestamps: true })
]
}, { timestamps: true });
Update:
I am trying this:
it gives me error message -
"message": "Converting circular structure to JSON"
const updated = account.update(
{
"_id" : ObjectId(accountId),
"buildings.gateways.devices.verificationCode": code
},
{
"$set": {
"buildings.$.gateways.0.devices.0.patientFirstName": "name1",
"buildings.$.gateways.0.devices.0.patientLastName": "name2",
}
}
)
Your help is appreciated. Thanks
UPDATED -
complete call for your reference.
// Register User
loginRouter.post('/register', async (req, res, next) => {
try {
var { email, userName, password, firstName, lastName, role, deviceIds, code } = req.body;
console.log(req.body)
// checking if email or username already exist before registering.
const verifyEmail = await User.find({
$or: [
{ 'email': email },
{ 'userName': userName },
]
})
if (verifyEmail.length > 0) {
throw new BadRequestError('DuplicateEmailOrUserName', {
message: 'Email or Username already exists'
});
}
// finding accountId for verification code first
const account = await Account.findOne({ "buildings.gateways.devices.verificationCode": code })
//console.log(account)
if (account.length === 0) {
console.log("Invalid registration code")
throw new BadRequestError('InvalidVerificationCode', {
message: 'Invalid registration code'
});
}
var accountId = account ? account._id : null
const buildings = account.buildings
const gateways = buildings[0].gateways;
const devices = gateways[0].devices;
//console.log("devices", devices)
// finding deviceId to insert for user from that account
const device = _.filter(devices, d => d.verificationCode === code);
// console.log("device", device)
if (!deviceIds) {
deviceIds = device.map(item => item._id)
// console.log("deviceIds", deviceIds)
}
const hashedPassword = await hasher.hashPassword(password);
const newUser = new User({
accountId: accountId ? accountId : undefined,
userName: userName,
password: hashedPassword,
email: email,
firstName: firstName,
lastName: lastName,
role: role,
refreshToken: uuidv4(),
refreshTokenExpiryDate: moment().add(process.env.REFRESH_TOKEN_EXPIRY_IN_DAYS, 'days'),
deviceIds: deviceIds ? deviceIds : [],
isActive: true,
});
const newlySavedUser = await newUser.save();
const {
refreshToken,
refreshTokenExpiryDate,
password: pwd,
...userWithoutSensitiveInfo
} = newlySavedUser.toObject();
**// solutions by #SuleymanSah** <----
try {
let result = await Account.findByIdAndUpdate(
accountId,
{
$set: {
"buildings.$[building].gateways.$[gateway].devices.$[device].patientFirstName": "userName"
}
},
{
arrayFilters: [
{ "building._id": ObjectId("5d254bb179584ebcbb68b712") },
{ "gateway._id": ObjectId("5d254b64ba574040d9632ada") },
{ "device.verificationCode": "4144" }
],
new: true
}
);
if (!result) return res.status(404);
console.log(result)
//res.send(result);
} catch (err) {
console.log(err);
res.status(500).send("Something went wrong");
}
res.json(newlySavedUser);
next();
} catch (err) {
next(err);
}
});
Let me know if you need more information. Thanks
You can use the filtered positional operator $ for this.
Note that we also need to have the buildingId and gatewayId to make it work dynamically.
router.put("/account/:accountId/:buildingId/:gatewayId", async (req, res) => {
const { patientFirstName, verificationCode } = req.body;
try {
let result = await Account.findByIdAndUpdate(
req.params.accountId,
{
$set: {
"buildings.$[building].gateways.$[gateway].devices.$[device].patientFirstName": patientFirstName
}
},
{
arrayFilters: [
{ "building._id": req.params.buildingId },
{ "gateway._id": req.params.gatewayId },
{ "device.verificationCode": verificationCode }
],
new: true
}
);
if (!result) return res.status(404);
res.send(result);
} catch (err) {
console.log(err);
res.status(500).send("Something went wrong");
}
});
TEST
Let's have this document:
{
"_id" : ObjectId("5e0da052b4b3fe5188602e11"),
"apiCallCount" : 1,
"email" : "abc#def.net",
"password" : "123123",
"userName" : "username",
"companyName" : "companyName",
"apiKey" : "apiKey",
"solutionType" : "solutionType",
"parentCompany" : "parentCompany",
"buildings" : [
{
"_id" : ObjectId("5e0da052b4b3fe5188602e12"),
"buildingName" : "buildingName 1",
"address" : "address",
"suite" : "suite",
"floor" : "floor",
"timeZone" : "String",
"gateways" : [
{
"_id" : ObjectId("5e0da052b4b3fe5188602e13"),
"gatewayName" : "gatewayName 1",
"gatewayKey" : "gatewayKey",
"suite" : "suite",
"devices" : [
{
"_id" : ObjectId("5e0da052b4b3fe5188602e15"),
"serialNumber" : "serialNumber 1",
"area" : "area",
"connectionStatus" : 0,
"gatewayKey" : "gatewayKey",
"applicationNumber" : 11,
"firmwareVersion" : "firmwareVersion",
"needsAttention" : true,
"verificationCode" : "123456",
"patientRiskStatus" : "patientRiskStatus",
"patientFirstName" : "patientFirstName",
"patientLastName" : "patientLastName",
"createdAt" : ISODate("2020-01-02T10:48:34.287+03:00"),
"updatedAt" : ISODate("2020-01-02T10:48:34.287+03:00")
},
{
"_id" : ObjectId("5e0da052b4b3fe5188602e14"),
"serialNumber" : "serialNumber 2",
"area" : "area",
"connectionStatus" : 0,
"gatewayKey" : "gatewayKey",
"applicationNumber" : 22,
"firmwareVersion" : "firmwareVersion",
"needsAttention" : true,
"verificationCode" : "987654",
"patientRiskStatus" : "patientRiskStatus",
"patientFirstName" : "patientFirstName",
"patientLastName" : "patientLastName",
"createdAt" : ISODate("2020-01-02T10:48:34.288+03:00"),
"updatedAt" : ISODate("2020-01-02T10:48:34.288+03:00")
}
],
"createdAt" : ISODate("2020-01-02T10:48:34.288+03:00"),
"updatedAt" : ISODate("2020-01-02T10:48:34.288+03:00")
}
],
"createdAt" : ISODate("2020-01-02T10:48:34.288+03:00"),
"updatedAt" : ISODate("2020-01-02T10:48:34.288+03:00")
}
],
"createdAt" : ISODate("2020-01-02T10:48:34.289+03:00"),
"updatedAt" : ISODate("2020-01-02T10:48:34.289+03:00"),
"__v" : 0
}
To update the device patientFirstName with verificationCode 123456, we need to send a PUT request to the url http://..../account/5e0da052b4b3fe5188602e11/5e0da052b4b3fe5188602e12/5e0da052b4b3fe5188602e13
5e0da052b4b3fe5188602e11 is accountId.
5e0da052b4b3fe5188602e12 is buildingId.
5e0da052b4b3fe5188602e13 is gatewayId.
Request body:
{
"verificationCode": "123456",
"patientFirstName": "UPDATED!!!"
}
Result will be like this:
{
"apiCallCount": 1,
"_id": "5e0da052b4b3fe5188602e11",
"email": "abc#def.net",
"password": "123123",
"userName": "username",
"companyName": "companyName",
"apiKey": "apiKey",
"solutionType": "solutionType",
"parentCompany": "parentCompany",
"buildings": [
{
"gateways": [
{
"devices": [
{
"_id": "5e0da052b4b3fe5188602e15",
"serialNumber": "serialNumber 1",
"area": "area",
"connectionStatus": 0,
"gatewayKey": "gatewayKey",
"applicationNumber": 11,
"firmwareVersion": "firmwareVersion",
"needsAttention": true,
"verificationCode": "123456",
"patientRiskStatus": "patientRiskStatus",
"patientFirstName": "UPDATED!!!",
"patientLastName": "patientLastName",
"createdAt": "2020-01-02T07:48:34.287Z",
"updatedAt": "2020-01-02T07:48:34.287Z"
},
{
"_id": "5e0da052b4b3fe5188602e14",
"serialNumber": "serialNumber 2",
"area": "area",
"connectionStatus": 0,
"gatewayKey": "gatewayKey",
"applicationNumber": 22,
"firmwareVersion": "firmwareVersion",
"needsAttention": true,
"verificationCode": "987654",
"patientRiskStatus": "patientRiskStatus",
"patientFirstName": "patientFirstName",
"patientLastName": "patientLastName",
"createdAt": "2020-01-02T07:48:34.288Z",
"updatedAt": "2020-01-02T07:48:34.288Z"
}
],
"_id": "5e0da052b4b3fe5188602e13",
"gatewayName": "gatewayName 1",
"gatewayKey": "gatewayKey",
"suite": "suite",
"createdAt": "2020-01-02T07:48:34.288Z",
"updatedAt": "2020-01-02T07:48:34.288Z"
}
],
"_id": "5e0da052b4b3fe5188602e12",
"buildingName": "buildingName 1",
"address": "address",
"suite": "suite",
"floor": "floor",
"timeZone": "String",
"createdAt": "2020-01-02T07:48:34.288Z",
"updatedAt": "2020-01-02T07:48:34.288Z"
}
],
"createdAt": "2020-01-02T07:48:34.289Z",
"updatedAt": "2020-01-02T09:10:25.200Z",
"__v": 0
}
And if you always want to update in the first building's in the first gateway, you may use this:
router.put("/account/:accountId", async (req, res) => {
const { patientFirstName, verificationCode } = req.body;
try {
let result = await Account.findByIdAndUpdate(
req.params.accountId,
{
$set: {
"buildings.0.gateways.0.devices.$[device].patientFirstName": patientFirstName
}
},
{
arrayFilters: [{ "device.verificationCode": verificationCode }],
new: true
}
);
if (!result) return res.status(404);
res.send(result);
} catch (err) {
console.log(err);
res.status(500).send("Something went wrong");
}
});
Now you need to send only the accountId in the url like this: http://../account/5e0da052b4b3fe5188602e11

How to make MongoDB Text Search Find Partial Matches

The find() at the end of this code does NOT find the inserted document.
Note: I'm using MongoDB version 3.2.15.
Why not?
How can I change the index or search parameters so that it's found?
db['test'].createIndex(
{
name: 'text',
email: 'text',
phoneNumber: 'text'
},
{
default_language: 'none',
name: 'text_index',
$caseSensitive: false,
$diacriticSensitive: false,
} );
db['test'].insert({
name: 'Chaney Waste Management',
email: 'cliffchaney#gmail.com',
phoneNumber: '(402)555-1212'
});
db['test'].find( {
$text:
{
$search: 'manage',
$language: 'none',
$caseSensitive: false,
$diacriticSensitive: false
}
} ); // Finds nothing! Why?
In case it matters, here is the full result of a db.hostInfo() call:
{
"system" : {
"currentTime" : ISODate("2017-11-09T02:38:45.379Z"),
"hostname" : "Cliff-Surface:3001",
"cpuAddrSize" : 64,
"memSizeMB" : 16310,
"numCores" : 4,
"cpuArch" : "x86_64",
"numaEnabled" : false
},
"os" : {
"type" : "Windows",
"name" : "Microsoft Windows 8",
"version" : "6.2 (build 9200)"
},
"extra" : {
"pageSize" : NumberLong(4096)
},
"ok" : 1.0
}

mongodb query select multiple subset from a document

{
_id: ObjectId("52ca2d45b80de42808000001"),
id: "1111139048239",
name: "Bruce Lim",
first_name: "Bruce",
last_name: "Lim",
friends: [
{
id: "1913681",
name: "John Sim",
icon: "https://fbcdn-profile-a.akamaihd.net/hprofile-ak-ash2/1117702_1913681_1171369396_q.jpg",
photos: [
{
src: "https://scontent-a.xx.fbcdn.net/hphotos-ash2/t1/230718_10150181312510976_7606555_n.jpg",
lat: "38.2289",
lng: "-85.7495"
},
{
src: "https://scontent-b.xx.fbcdn.net/hphotos-frc3/230480_10150181312620976_3864544_n.jpg",
lat: "38.2289",
lng: "-85.7495"
}
]
},
{
id: "31925743892",
name: "Mike Holloway",
icon: "https://fbcdn-profile-a.akamaihd.net/hprofile-ak-prn2/211634_31925743892_1471358831_q.jpg",
photos: [
{
src: "https://scontent-a.xx.fbcdn.net/hphotos-ash2/t1/230718_10150181312510976_7606555_n.jpg",
lat: "38.2289",
lng: "-85.7495"
},
{
src: "https://scontent-b.xx.fbcdn.net/hphotos-frc3/230480_10150181312620976_3864544_n.jpg",
lat: "38.2289",
lng: "-85.7495"
}
]
},
{
id: "1954048",
name: "Christiana Basset",
icon: "https://fbcdn-profile-a.akamaihd.net/hprofile-ak-prn2/211634_1954048_1471358831_q.jpg",
photos: [
{
src: "https://scontent-a.xx.fbcdn.net/hphotos-ash2/t1/230718_10150181312510976_7606555_n.jpg",
lat: "38.2289",
lng: "-85.7495"
},
{
src: "https://scontent-b.xx.fbcdn.net/hphotos-frc3/230480_10150181312620976_3864544_n.jpg",
lat: "38.2289",
lng: "-85.7495"
}
]
}
]
}
when I query a collection of these docs this with
db.mapping.find(
{"id":"1111139048239"},
{"friends":{
$elemMatch:{"id":"1913681"}
}}
)
I get one matching friend subset back.
{
"_id" : ObjectId("52ca2d45b80de42808000001"),
"friends" : [
{
"id" : "1913681",
"name" : "John Sim",
"icon" : "https://fbcdn-profile-a.akamaihd.net/hprofile-ak-ash2/1117702_1913681_1171369396_q.jpg",
"photos" : [
{
"src" : "https://scontent-a.xx.fbcdn.net/hphotos-ash2/t1/230718_10150181312510976_7606555_n.jpg",
"lat" : "38.2289",
"lng" : "-85.7495"
},
{
"src" : "https://scontent-b.xx.fbcdn.net/hphotos-frc3/230480_10150181312620976_3864544_n.jpg",
"lat" : "38.2289",
"lng" : "-85.7495"
}
]
}
]
}
How do I select multiple subsets.
db.mapping.find(
{"id":"1111139048239"},
{"friends":{
$elemMatch:{"id":"1913681", "id":"1954048"}
}}
)
db.mapping.find(
{"id":"1111139048239"},
{"friends":{
$elemMatch:{"id":"1913681"},
$elemMatch:{"id":"1954048"}
}}
)
gets me only the last match, which is 1954048 in this case. How do I get both - 1913681, 1954048?
The general syntax of find in mongodb is
db.collection.find(<criteria>,<projection>)
In your case,
criteria: id should be "1111139048239"
projection: listing friends who have id 1913681, 1954048
elemMatch can get only the first existence of the element and also when multiple values are given for same attribute it will display only the last executed elemMatch within the document.
I would suggest you to go with aggregation. It will help you to get the required output.
db.mapping.aggregate([
{$match:{id:"1111139048239"}}, // STEP 1
{$unwind:"$friends"}, // STEP 2
{$match:{"friends.id":{$in:["1913681","1954048"]}}} // STEP 3
])
Execution:
STEP 1: Selects the document with id "1111139048239"
STEP 2: Unwinds the friends array in the selected document and
create multiple documents as per the size of friends array.
In this case 3 documents.
STEP 3: Select documents which has a friends id "1913681", "1954048".
In this case 2 documents will be selected. Append values to array to get
more documents as output
{"friends.id":{$in:["1913681","1954048",etc]}

Pivoting data in MongoDB

I have an 'articles' collection, some sample data might look like this:
[
{body: 'Interesting news in Siberia and so on etc. etc. etc. and lolcats too',
author: 'John Doe',
tags: [{tid:24, name: "Siberia"},
{tid: 5231, name: "Lolcats"},]
},
{body: 'Something is going on in Siberia and France',
author: 'Jane Doe',
tags: [{tid:24, name: "Siberia"},
{tid: 6432, name: "France"},]
},
]
And my required ouput is a distinct list of tags:
[
{tid: 24, name: 'Siberia'},
{tid: 5231, name: 'Lolcats'},
{tid: 6432, name: 'France'},
]
I have been struggling with some mapReduce queries and distinct aggregation, but without result.
The simplest way to do this is:
db.articles.distinct("tags")
If you want to use aggregation framework (new in 2.2) it's a little longer:
db.articles.aggregate([{$unwind:"$tags"},
{$group:{_id:"$tags"}},
{$project:{tid:"$_id.tid",name:"$_id.name",_id:0}}
]).result
In mongo v2.2 you can do this with the aggregate function:
db.articles.aggregate([
{
// From each document, emit just the tags
$project: {
tags: 1
}
}, {
// Duplicate each document for each tags element it contains
$unwind: '$tags'
}, {
// Group the documents by the tag's tid and name
$group: {
_id: { tid: '$tags.tid', name: '$tags.name' }
}
}, {
// Reshape the document to exclude the _id and bring tid and name to the top level
$project: {
_id: 0,
tid: '$_id.tid',
name: '$_id.name'
}
}],
function (err, result) {
if (err) {
console.log('aggregation error: %s', err);
} else {
console.dir(result);
}
});
For your documents, this produces the following output:
[ { tid: 6432, name: 'France' },
{ tid: 5231, name: 'Lolcats' },
{ tid: 24, name: 'Siberia' } ]
db.articles.distinct("tags")
gives the following output:
[
{
"tid" : 24,
"name" : "Siberia"
},
{
"tid" : 5231,
"name" : "Lolcats"
},
{
"tid" : 6432,
"name" : "France"
}
]