【问题标题】:What is the best way to keep track of changes of a document's property in MongoDB?跟踪 MongoDB 中文档属性更改的最佳方法是什么?
【发布时间】:2020-01-03 06:34:42
【问题描述】:

我想知道如何在 MongoDB 中跟踪文档的值。

这是一个带有 Node 和 Express 后端的 MongoDB 数据库。

假设我有一个文档,它是患者集合的一部分。

{
    "_id": "4k2lK49938d82kL",
    "firstName": "John",
    "objective": "Burn fat"
}

然后我编辑“objective”属性,所以文档结果如下:

{
    "_id": "4k2lK49938d82kL",
    "firstName": "John",
    "objective": "Gain muscle"
}

跟踪这种变化的最佳/最有效的方法是什么?换句话说,我想知道“objective”属性在过去具有“Burn fat”的值,并在将来访问它。

非常感谢!

【问题讨论】:

标签: javascript node.js reactjs mongodb backend


【解决方案1】:

将其作为子文档维护,如下所示

{
    "_id": "4k2lK49938d82kL",
    "firstName": "John",
    "objective": {
        obj1: "Gain muscle",
        obj2: "Burn fat"
    }

}

您也可以将其作为数组字段进行维护,但请记住,mongodb 不允许您在数组字段中维护唯一性,如果您打算索引“目标”字段,则必须创建一个多键索引

【讨论】:

  • 不错的选择,您能否详细解释一下数组中的唯一性?我不熟悉这个概念。非常感谢Anban
  • 在数组字段中,您可以维护重复值,因此您无法在其上建立唯一索引。在 mongo 中,唯一约束适用于单独的文档,对于唯一的多键索引,只要该文档的索引键值不与另一个文档的索引键值重复,文档可能具有导致重复索引键值的数组元素
【解决方案2】:

也许您可以将“目标”的类型更改为数组并跟踪其中的更改。数组的最后一个是最新的值。

【讨论】:

  • 我一开始就是这么想的。你觉得这有什么缺点吗?这些属性不会经常编辑,所以我不担心文档大小。任何进一步的评论都非常感谢。谢谢刘!
【解决方案3】:

我认为最简单的解决方案是使用和更新数组:

const patientSchema = new Schema({
  firstName: { type: String, required: true },
  lastName: { type: String, required: true },
  objective: { type: String, required: true }
  notes: [{
    date: { type: Date, default: Date.now() },
    note: { type: String, required: true }
  }],
});

那么当你想更新目标时……

const updatePatientObjective = async (req, res) => {
  try {
    // check if _id and new objective exist in req.body
    const { _id, objective, date } = req.body;
    if (!_id || !objective) throw "Unable to update patient's objective.";

    // make sure provided _id is valid
    const existingPatient = await Patient.findOne({ _id });
    if (!existingPatient) throw "Unable to locate that patient.";

    // pull out objective as previousObjective 
    const { objective: previousObjective } = existingPatient;

    // update patient's objective while pushing 
    // the previous objective into the notes sub document
    await existingPatient.updateOne({ 
        // update current objective
        $set { objective },
        // push an object with a date and note (previouseObjective) 
        // into a notes array
        $push: { 
          notes: {
            date,
            note: previousObjective              
         },
        },  
      }),
    );

    // send back response
    res
      .status(201)
      .json({ message: "Successfully updated your objective!" });
  } catch (err) {
    return res.status(400).json({ err: err.toString() });
  }
};

文档将如下所示:

firstName: "John",
lastName: "Smith",
objective: "Lose body fat.",
notes: [
  {
    date: 2019-07-19T17:45:43-07:00,
    note: "Gain muscle".
  },
  { 
    date: 2019-08-09T12:00:38-07:00,
    note: "Work on cardio."
  }
  { 
    date: 2019-08-29T19:00:38-07:00,
    note: "Become a fullstack web developer."
  }
  ...etc
]

或者,如果您担心文档大小,则为患者历史创建一个单独的架构并引用用户的 id(或者只是将患者的 _id 存储为字符串而不是引用 ObjectId,无论您喜欢哪个):

const patientHistorySchema = new Schema({
  _id: { type: Schema.Types.ObjectId, ref: "Patient", required: true },
  objective: { type: String, required: true }
});

然后在目标更新时创建一个新的患者历史文档...

PatientHistory.create({ _id, objective: previousObjective });

如果您需要访问患者病史文件...

PatientHistory.find({ _id });

【讨论】:

  • 感谢您的详细解答!我最初的想法是将objective 字段转换为数组(正如刘文哲在下面的评论中建议的那样),并进行以下操作:objective: [{ objectiveText: "Gain muscle", timestamp: 2019-08-29T19:00:38-07:00 }] 这样每次编辑都会有一个objectiveText 值和一个时间戳,我可以检索最后一个通过对这些日期进行排序。对于您的第一个提案(带有注释数组的那个),您会说这种方法的优缺点是什么?再次感谢马特!
  • 我能想到的唯一问题是我可以在 1 分钟内编辑属性 3 次(比如用户在 UI 中单击了 3 次按钮),因此目标数组将被填充有 2 个值实际上并不代表有价值的数据(可能是用户在搞乱)。为了防止这种情况,我考虑运行一个每日脚本,删除那些没有价值的 2 个“额外值”,并保留当天的最后一个。这是因为房产的性质:如果你选择减肥,那可能是你接下来几个月的目标。你有什么想法?
  • 您可以根据自己的需要进行操作。但是,如果您只想/关心最新的previousObjective,那么只需覆盖数组(使用$set 而不是$push)或简单地使用一个对象并更新其属性。至于防止重复,在更新文档之前,只需检查:if(objective === previousObjective) throw "Unable to update objective. A new objective must be supplied."
  • 事实上,我关心历史以及这些更改是何时产生的,以便对这些属性执行数据分析。是的,我想我会坚持数组建议。关于用户在一分钟内点击 3 次按钮的情况有什么想法吗?这让我有点担心。
  • 是的,请参阅我上面评论中的最后一句话。如果提交的目标相同,则可以抛出错误或简单地将existingPatient.updateOne 包裹在类似的条件中以忽略它。
【解决方案4】:

不建议在同一文档中维护/跟踪历史记录。随着文档大小将不断增加,导致

  • 可能如果更新太多,16mb 文档大小限制
  • 性能下降

相反,您应该为历史维护一个单独的集合。您可能已经使用 hibernates 的 Javers 或 envers 来审计您的关系数据库。如果没有,您可以检查它们是如何工作的。为每个表 (xyz) 维护一个单独的表 (xyz_AUD)。对于 xyz 表中的每一行(主键 abc),xyz_AUD 表中存在多行,其中每一行是该行的版本。

此外,Javers 还支持 MongoDB 审计。如果您使用的是java,则可以直接使用它。无需自己编写逻辑。

参考 - https://nullbeans.com/auditing-using-spring-boot-mongodb-and-javers/

还有一点,Javers Envers Hibernate 是 java 库。但我敢肯定,对于其他编程语言,也会出现类似的库。

还有一个猫鼬插件-

【讨论】:

  • 我并不担心文档大小,因为我想跟踪的属性不会经常被编辑。我会说为历史保存一个不同的集合需要更多的存储空间(如果我错了,请纠正我,这是我的假设),因为每次编辑都是一个新对象。感谢您的帮助拉贾特
猜你喜欢
  • 2016-06-14
  • 1970-01-01
  • 2010-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-05
  • 2016-03-30
  • 1970-01-01
相关资源
最近更新 更多