Mongo Java:在一个命令中设置值并推送到包含先前值的数组

Mongo Java: Set values and push to array with previous values in one command

我想在 upsert 期间将之前的状态(如果值发生变化)推送到数组 history (atomar) 但无论我尝试什么,我都找不到一个干净的解决方案。

下面是简化的尝试:

尝试 1:使用 $set$concatArrays

final var timestamp = Document.parse(String.format("{$set: {timestamp: %d}}", time));
final var pointCount = Document.parse("{$set: {" +
            "data_point_count: {$ifNull: [{$add: [\"$data_point_count\", 1]}, 1]}   }}");

final var history = Document.parse(String.format("{$set: {" +
   "history: {$cond: {" +
      "if: {$eq: [\"$timestamp\", %d]}," +
      "then: \"$history\"," +               // no changes, keep untouched
      "else: {$concatArrays: [" +
         "{$ifNull: [\"$history\", []]}," + // without, history stays null

         // Bug: this adds an empty object on 1st upsert (insert)
         "[{timestamp: \"$timestamp\", data_point_count: \"$data_point_count\"}]" +
      "]}" +
   "}}" +
"}}", time));

final var update = Arrays.asList(setHistory, timestampUpdate, meanNinetyPercentileUpdateCommand, setDataPointCount);
// If I don't use Arrays.asList() commands like `$ifNull` are written into the database
//final var update = Updates.combine(setDataPointCount, setHistory); does not interpret the commands

final var options = new UpdateOptions().upsert(true);
targetCollection.updateOne(filter, update, options);

问题:

尝试 2:使用 $set$push

final var update = new Document();

final var set = new Document();
set.put("timestamp", time);
set.put("data_point_count", Document.parse("{$ifNull: [{$add: [\"$data_point_count\", 1]}, 1]}"));
update.put("$set", set);

update.put("$push", Document.parse("{history: {timestamp: \"$timestamp\", data_point_count: \"$data_point_count\"}}}"));

final var options = new UpdateOptions().upsert(true);
targetCollection.updateOne(filter, update, options);

问题:

问题: 我如何使用一个 upsert 命令:


更新(来自@Gibbs 的建议):

如果我对你的建议理解正确,它还会在第一次执行更新插入时添加一个空对象:[{}, {12345, 2}, {23456, 3}]

final var setHistory = Document.parse(String.format("{"+
    "$set: {"+
        "history: {"+
            "$cond: ["+
                // <Array condition goes here>
                "{"+
                    "$eq: [\"$timestamp\", %d]"+ // with eq => insert also adds an empty object
                    //"$ne: [\"$timestamp\", %d]"+ // Error: "input to $map must be an array not string"
                "},"+
                //True part (timestamp not changed: keep history)
                "{"+
                    "$map: {"+
                        "input: \"history\","+
                        "in: {"+
                            "$mergeObjects: ["+
                                "\"$$this\","+
                                "{"+
                                    "timestamp: \"$timestamp\"," +
                                    "mean_ninety_percentile: \"$mean_ninety_percentile\""+
                                "}"+
                            "]"+
                        "}"+
                    "}"+
                "},"+
                //False part (timestamp changed: add state to history)
                "{"+
                    "$concatArrays: [" +
                        "{ $ifNull: [\"$history\", []] }," +
                        "[" +
                            "{"+
                                "timestamp: \"$timestamp\"," +
                                "mean_ninety_percentile: \"$mean_ninety_percentile\""+
                            "}"+
                        "]" +
                    "]"+
                "}"+
            "]"+
        "}"+
    "}"+
"}", time));

为了澄清,目标是在第 2 个 upsert(即 1 次插入和 1 次更新)之后的以下状态

collection = [
  {
     timestamp: 23456,
     data_point_count: 2,
     history: [
        {timestamp: 12345, data_point_count: 1}
     ]
  },...
]

您可以使用 aggregate update 实现相同的效果。但它支持 mongo 4.2+.

db.collection.update({},
[{
$set: {
  timestamp: 12345,
  mean_ninety_percentile: 0.111,
  history: {
    $cond: [
      // <Array condition goes here>
      {
        $eq: [
          "$timestamp",
          12345
        ]
      },
      // True part (timestamp not changed: keep history)
      "$history",
      // False part (timestamp changed: add state to history)
      {
        $concatArrays: [
          {
            $ifNull: [
              "$history",
              []
            ]
          },
          [
            // add current state to history
            {
              timestamp: "$timestamp",
              mean_ninety_percentile: "$mean_ninety_percentile"
            }
          ]
      }
    ]
  }
}
}],
{
  upsert: true
})