我的问题是如何最好地模拟三个对象,这三个对象由1对多和多对多关系的组合相关联。例如,在医疗诊所中,患者可以看到许多医生,并且医生可以治疗许多患者,医生可以为患者推荐许多测试,对于一个患者总是进行测试并且患者可以进行许多测试。这是我喜欢的,在患者中嵌入测试:
患者:
{
_id: ObjectID
patient_name: String
insurance: String
Doctors: [
doctor_name: String
]
Tests: [
{ test_name: string; test_result:string; doctor_name: string }
]
}
医生:
{
_id: ObjectID
doctor_name: String
Specialty: String
patients: [ patient_name: String ]
}
如果如上所述嵌入测试,则问题是医生名称可能在患者/医生和患者/测试中重复。考虑的另一种方法是有三个系列:患者,医生和测试,以便测试不会嵌入患者体内。这种方法的问题是当有人想要查看患者的图表时,他们会希望看到测试结果,这意味着更多的读取。关于在MongoDB中建模这种问题的最佳方法有哪些指导原则?
答案 0 :(得分:0)
与往常一样,它在很大程度上取决于您的使用案例。让我们假设几个简单的:
您希望将给定医生订购的所有测试列入给定患者。
您想列出给定医生订购的所有测试。
“给定”未偶然突出显示。它表示查询时已有的信息。
您正确地确定医生与患者的关系应该保存在患者文件中
至于病人,我们需要一系列医生。但医生可能有相同的名字。想想约翰米勒。那么我们怎样才能正确地识别任何给定的约翰米勒?是的,由一个唯一的ID。嘿,默认情况下,每个MonogDB文档都需要一个。所以,让我们从一个简单的模型开始:
{
_id: ObjectId,
name: String,
doctors: [ObjectId, ObjectId, ObjectId...],
}
然而,我们立刻发现了一个问题:您想在展示病人时显示医生姓名,对吗?使用上面的数据模型,您必须执行以下操作:
var patient = db.patient.find(some_query)
var patientsDoctors = db.doctors.find({_id:{$in:"$patient.doctors"}})
在这个用例中,这甚至可能是一个可行的解决方案,因为无论如何医生的_id
字段都被编入索引(因此第二个查询应该相当快),而数组$patient.doctors
相当小。但是,如果要保存该查询,我们必须通过稍微更改数据模型来交易(相对便宜)磁盘空间(相对难以获取)性能:
{
_id: ObjectId,
name: String,
doctors: [
{id:ObjectId,name: String},
{id:ObjectId,name: String},
{id:ObjectId,name: String},
...]
}
现在,当您向患者展示时,您可以立即列出医生姓名。如果需要更详细的医生观点,您可以通过他或她_id
积极地确定相关医生并相应地处理该医生。
现在找到所有接受过特定医生治疗的患者变得非常容易:
db.patients.find({"doctors.id": someObjectId})
但是,仍然存在一个问题:此查询实际上会生成collection scan,这意味着系统中的每个文档都将被读取并与您的查询条件进行比较。慢,因此很糟糕。我们需要创建一个索引:
db.patients.createIndex({"doctors.id":1})
并且result of .explain()
看起来像我们想要的那样:
> db.patients.find({"doctors.id":ObjectId("58c4fdf5b5aeccb7a2bfe727")}).explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "clinic.patients",
"indexFilterSet" : false,
"parsedQuery" : {
"doctors.id" : {
"$eq" : ObjectId("58c4fdf5b5aeccb7a2bfe727")
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"doctors.id" : 1
},
"indexName" : "doctors.id_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"doctors.id" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 1,
"direction" : "forward",
"indexBounds" : {
"doctors.id" : [
"[ObjectId('58c4fdf5b5aeccb7a2bfe727'), ObjectId('58c4fdf5b5aeccb7a2bfe727')]"
]
}
}
},
"rejectedPlans" : [ ]
},
// serverInfo ommited for brevity here
"ok" : 1
}
但是现在如果医生改变他或她的名字会发生什么?虽然这可能是一个罕见的用例(主要是婚姻和性别变化),但我们需要解决这个问题。但由于此用例 很少,我们可以使用相对较慢的查询来解决它:
db.patients.update(
{"doctors.id":ObjectId("58c4fdf5b5aeccb7a2bfe727")},
{"$set":{"doctors.$.name":"Bar Baz"}},
{multi:true}
)
现在看来我们的用例1已经建模,用例2也已经完成;)
主要问题是我们应该将测试存储在患者体内还是有自己的收集或类似于我们对医生的处理。这是一个相信和要求的问题。就个人而言,我会存储这样的测试:
{
_id: ObjectId,
patient: ObjectId,
doctor: ObjectId,
description: String,
date: ISODate,
// whatever you deem appropriate
}
这样,我们可以处理其余3个用例:
var patient = db.patient.find(someQuery)
var testsForPatient = db.tests.find({patient:"patient._id"})
您已经有患者和测试,现在您只需要另外加载医生的数据。如果您愿意,您也可以根据我们在患者模型中的表现来添加医生的姓名。
var doctor = db.doctors.find(someQueryForDoctor)
var patient = db.patients.find(someQueryForPatient)
var testsOfDoctorOnPatient = db.tests.find({patient:"$patient._id",doctor:"$doctor._id"})
您已经拥有患者和医生的详细信息,因此无需为此用例嵌入任何信息。
var doctor = db.doctors.find(someQueryForDoctor)
var testsofDoctor = db.tests.find({doctor:"$doctor._id"})
由于您出于统计目的,很可能需要这样的查询,因此将患者的详细信息嵌入测试中是没有意义的。
如果没有索引,对测试的查询将再次导致集合扫描。但是我们如何在这里索引。由于我们正在通过医生_id
或患者的_id
或两者进行搜索,因此compound index会派上用场:
db.tests.createIndex({patient:1,doctor:1})
使用此索引,上述测试集合上的所有查询都使用索引,因为您可以使用.explain()
方法验证自己。
至关重要的是,您要问自己,您拥有哪些数据以及您要查询哪些数据。一个很好的例子实际上是查询给定患者的测试。您真的是否需要立即使用医生的名字,或者“Ordered by”这样的按钮是否足以满足您的使用需求?
另一个经验法则是:记录在给定时间点发生的事情,并仅嵌入您绝对需要的内容。
希望这个冗长的例子可以帮助您了解如何在MongoDB中进行数据建模。