I am currently working on a project that has the following schema using mongoose.
User schema
const userSchema = {
name: string
email: string
medicalVisits: [{type: Schema.ObjectId, ref: "records"}]
createdAt: Date
}
Records schema
const recordSchema = {
medication: [String],
rating: Number
user: [{type: Schema.ObjectId, ref: "user"}]
tests: [{type: Schema.ObjectId, ref: "tests"}]
createdAt: Date
}
Tests schema
testScore: Number
answers: Object
user: [{type: Schema.ObjectId, ref: "user"}]
createdAt: Date
From the little schema above, I have a setup where a patient can take tests multiple times and their respective tests are saved in the Tests collection
. Also, the date
is recorded for all tests they take. A doctor can request to see a patient's record, in this case, the patient has only one record document
that has their tests records
embedded in them. Currently, I am faced with the problem of getting a patient's newest and oldest test score alongside their initial details.
I can do a mongoose populate to get all information regarding a user, eg
await User.findById(userId).populate({
path: "medicalVisits"
model: "records"
populate: {
path: "tests"
model: "test"
}
})
And that operation returns the patient's record and all the tests they have taken since they signed up to date. But when I make such a call to the Database, I just want to retrieve the patient's newest and oldest score. In other words, I want to get the patients, Initial test score
, and their most recent test score. I am new to Mongoose aggregation
, I tried to use the Mongoose aggregate
function, but it returns an empty array, I guess I am missing something.
Currently, this is what my aggregate pipeline looks like.
const user = await Doctor.aggregate([
{ $match: { _id: docId } },
{
$lookup: {
from: "users",
localField: "patients",
foreignField: "_id",
as: "patients",
},
},
{ $unwind: "$patients" },
{ $unwind: "$patients.medicalVisits" },
{
$lookup: {
from: "records",
localField: "patients.user",
foreignField: "_id",
as: "patientRecord",
},
},
{ $unwind: "$patientRecord" },
// { $sort: { createdAt: 1 } },
{
$group: {
_id: docId,
user: { $last: "$patients" },
record: { $last: "$patientRecord"}
},
},
]);
return user[0];
From the above snippet, my intention is: given a doctor Id
, they can see a list of their patients and also see their newest and oldest test score.
Expected Output
const output = {
userId: 6e12euido....
name: "John doe"
email: "john@john.com"
rating: 2
initialTestScore: 10
recentTestScore: 30
}
How do I go about this? Or what could be a better alternative? Thank you very much.
tried my best to understand your case, and I think your aggregation pipeline should be like:
const patientsWithNewestRecord = await Doctor.aggregate([
{ $match: { _id: docId } },
{
$lookup: {
from: "users",
localField: "patients",
foreignField: "_id",
as: "patients",
},
},
// one patient, per doc
{ $unwind: "$patients" },
// one patient with all his/her visit records, per doc
{
$lookup: {
from: "records",
localField: "patients.medicalVisits",
foreignField: "_id",
as: "patientRecords",
},
},
// one patient with one visit record, per doc
{ $unwind: "$patientRecords" },
// sort by patient first, createdAt second
{ $sort: { 'patientRecords.user': 1, 'patientRecords.createdAt': 1 } },
{
$group: {
_id: { patient: '$patientRecords.user' },
user: { $last: "$patients" },
record: { $last: "$patientRecords"}
},
},
]);
this pipeline return a list of a doctor's patients and also see their newest test record. Oldest test record should be in similar war.
Based on these collections (as I understand them from your question):
// doctor collection:
{ _id: "doc1", patients: ["user1"] }
// user collection:
{
_id: "user1", name: "John", email: "john@gmail.com",
medicalVisits: ["record1", "record2"]
}
// record collection:
{ _id: "record1", rating: 2, tests: ["test1", "test2"] }
{ _id: "record2", rating: 4, tests: ["test3"] }
// test collection:
{ _id: "test1", testScore: 12, createdAt: ISODate("2021-12-04") }
{ _id: "test2", testScore: 9, createdAt: ISODate("2021-12-05") }
{ _id: "test3", testScore: 15, createdAt: ISODate("2021-12-24") }
we can apply:
db.doctor.aggregate([
{ $match: { _id: "doc1" } }
{ $lookup: {
from: "user",
localField: "patients", foreignField: "_id",
as: "patients"
}},
{ $unwind: "$patients" }, { $unwind: "$patients.medicalVisits" },
{ $lookup: {
from: "record",
localField: "patients.medicalVisits", foreignField: "_id",
as: "records"
}},
{ $unwind: "$records" }, { $unwind: "$records.tests" },
{ $lookup: {
from: "test",
localField: "records.tests", foreignField: "_id",
as: "tests"
}},
{ $unwind: "$tests" },
{ $sort: { "tests.createdAt": 1 } },
{ $group: {
_id: "$patients._id",
name: { $first: "$patients.name" },
email: { $first: "$patients.email" },
rating: { $first: "$records.rating" },
initialTestScore: { $first: "$tests.testScore" },
recentTestScore: { $last: "$tests.testScore" }
}},
{ $set: { "userId": "$_id" } }, { $unset: "_id" }
])
in order to extract:
{
userId: "user1",
name: "John",
email: "john@gmail.com",
rating: 2,
initialTestScore: 12,
recentTestScore: 15
}
Differences compared to your query:
$lookup
the test
collection as it seems you information from there to get both test dates and test scores.$sort
by test date ( createdAt
) before the $group
by user such that we'll be able to define the right order for selecting the $first
and $last
test scores.$first
on each group on user's field (since all unwind records for a given user have the same user information): for instance email: { $first: "$patients.email" }
$first
and $last
test scores for a user as defined by the $sort
order: initialTestScore: { $first: "$tests.testScore" }
and recentTestScore: { $last: "$tests.testScore" }
.$set
/ $unset
to rename the _id
field into userId
I would suggest to do the following once you have the userId/patientId: -
If you can just retireve the user details and all the tests without any sorting, then you can proceed the following way : -
You will not be performing the operations on DB end, so there might be a minor speed issue, but the difference would still come out to be in milliseconds unless a user takes a billion tests.
Let me know if this helps, let me know if it doesnt
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.