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);
问题:
- 当
upsert
第一次执行时(即插入),一个空对象被添加到历史:[{}]
- 在后续更新中,一切正常:
[{}, {12345, 2}, {23456, 3}]
尝试 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);
问题:
- 不解释像
\"$timestamp\"
和 $ifNull
这样的命令,只写例如$ifNull
进入数据库。
- 我不能使用
Arrays.asList()
否则我会收到错误消息:Unrecognized pipeline stage name: '$push'
,但如果没有 Arrays.asList
,命令不会执行,只会写入数据库,例如data_point_count: {$ifNull: ...}
问题:
我如何使用一个 upsert
命令:
- 在插入期间创建
history: []
或 history: null
或只是没有 history
字段
- 当
update
中的时间戳发生变化时,将更新对象之前的状态添加到 history
数组中,不包含任何空对象(参见尝试 1)。
更新(来自@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
})
我想在 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);
问题:
- 当
upsert
第一次执行时(即插入),一个空对象被添加到历史:[{}]
- 在后续更新中,一切正常:
[{}, {12345, 2}, {23456, 3}]
尝试 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);
问题:
- 不解释像
\"$timestamp\"
和$ifNull
这样的命令,只写例如$ifNull
进入数据库。 - 我不能使用
Arrays.asList()
否则我会收到错误消息:Unrecognized pipeline stage name: '$push'
,但如果没有Arrays.asList
,命令不会执行,只会写入数据库,例如data_point_count: {$ifNull: ...}
问题:
我如何使用一个 upsert
命令:
- 在插入期间创建
history: []
或history: null
或只是没有history
字段 - 当
update
中的时间戳发生变化时,将更新对象之前的状态添加到history
数组中,不包含任何空对象(参见尝试 1)。
更新(来自@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
})