I am trying to use Spring Data Mongo to take the average of some fields in a collection of documents. I have a pipeline that searches via at Atlas search and then a group stage that performs the average.
<code> {
"_id": {"$oid": "65ea0c9d638cd57f51c22cb7"},
"listing": {
"listingId": 709834373,
"listDate": {"$date": "2023-10-12T00:00:00.000Z"},
"price": {
"currencyCode": "USD",
"value": 1000
},
}
}
</code>
<code> {
"_id": {"$oid": "65ea0c9d638cd57f51c22cb7"},
"listing": {
"listingId": 709834373,
"listDate": {"$date": "2023-10-12T00:00:00.000Z"},
"price": {
"currencyCode": "USD",
"value": 1000
},
}
}
</code>
{
"_id": {"$oid": "65ea0c9d638cd57f51c22cb7"},
"listing": {
"listingId": 709834373,
"listDate": {"$date": "2023-10-12T00:00:00.000Z"},
"price": {
"currencyCode": "USD",
"value": 1000
},
}
}
<code> public Optional<SummaryData> searchResultsSummaryData(SearchFilter searchFilter) {
SearchOperation searchOperation = buildSearchOperationFromFilter(searchFilter);
SearchAggregation searchAggregation = SearchAggregation.builder()
.searchOperation(searchOperation)
.build();
Document aggregationDoc = new Document();
mongoConverter.write(searchAggregation, aggregationDoc);
GroupOperation groupOperation = group("listing.price.value").avg("$avg(listing.price.value)").as("avgPrice");
Document groupDoc = new Document();
mongoConverter.write(groupOperation, groupDoc);
log.debug("groupDoc: {}", groupDoc.toJson());
Document groupStage = new Document("$group", groupDoc);
Document projectDocDetail = new Document("avgPrice", 1);
Document projectDoc = new Document("$project", projectDocDetail);
MongoCollection<Document> collection = mongoOperations.getCollection(LISTING_DOCUMENT.collection());
List<Document> aggList = List.of(aggregationDoc, groupStage, projectDoc);
log.debug("aggList: {}", toJson(aggList));
AggregateIterable<Document> aggregationResult = collection.aggregate(aggList);
Document summaryDocument = aggregationResult.first();
if (summaryDocument == null) {
return Optional.empty();
} else {
return Optional.of(defaultMongoConverter.read(SummaryData.class, summaryDocument));
}
}
</code>
<code> public Optional<SummaryData> searchResultsSummaryData(SearchFilter searchFilter) {
SearchOperation searchOperation = buildSearchOperationFromFilter(searchFilter);
SearchAggregation searchAggregation = SearchAggregation.builder()
.searchOperation(searchOperation)
.build();
Document aggregationDoc = new Document();
mongoConverter.write(searchAggregation, aggregationDoc);
GroupOperation groupOperation = group("listing.price.value").avg("$avg(listing.price.value)").as("avgPrice");
Document groupDoc = new Document();
mongoConverter.write(groupOperation, groupDoc);
log.debug("groupDoc: {}", groupDoc.toJson());
Document groupStage = new Document("$group", groupDoc);
Document projectDocDetail = new Document("avgPrice", 1);
Document projectDoc = new Document("$project", projectDocDetail);
MongoCollection<Document> collection = mongoOperations.getCollection(LISTING_DOCUMENT.collection());
List<Document> aggList = List.of(aggregationDoc, groupStage, projectDoc);
log.debug("aggList: {}", toJson(aggList));
AggregateIterable<Document> aggregationResult = collection.aggregate(aggList);
Document summaryDocument = aggregationResult.first();
if (summaryDocument == null) {
return Optional.empty();
} else {
return Optional.of(defaultMongoConverter.read(SummaryData.class, summaryDocument));
}
}
</code>
public Optional<SummaryData> searchResultsSummaryData(SearchFilter searchFilter) {
SearchOperation searchOperation = buildSearchOperationFromFilter(searchFilter);
SearchAggregation searchAggregation = SearchAggregation.builder()
.searchOperation(searchOperation)
.build();
Document aggregationDoc = new Document();
mongoConverter.write(searchAggregation, aggregationDoc);
GroupOperation groupOperation = group("listing.price.value").avg("$avg(listing.price.value)").as("avgPrice");
Document groupDoc = new Document();
mongoConverter.write(groupOperation, groupDoc);
log.debug("groupDoc: {}", groupDoc.toJson());
Document groupStage = new Document("$group", groupDoc);
Document projectDocDetail = new Document("avgPrice", 1);
Document projectDoc = new Document("$project", projectDocDetail);
MongoCollection<Document> collection = mongoOperations.getCollection(LISTING_DOCUMENT.collection());
List<Document> aggList = List.of(aggregationDoc, groupStage, projectDoc);
log.debug("aggList: {}", toJson(aggList));
AggregateIterable<Document> aggregationResult = collection.aggregate(aggList);
Document summaryDocument = aggregationResult.first();
if (summaryDocument == null) {
return Optional.empty();
} else {
return Optional.of(defaultMongoConverter.read(SummaryData.class, summaryDocument));
}
}
But when I run this, I get this exception:
<code>com.mongodb.MongoCommandException: Command failed with error 40234 (Location40234): 'The field 'idFields' must be an accumulator object' on server pl-1-us-west-2.c8foo.mongodb.net:1037. The full response is {"ok": 0.0, "errmsg": "The field 'idFields' must be an accumulator object", "code": 40234, "codeName": "Location40234", "$clusterTime": {"clusterTime": {"$timestamp": {"t": 1719546021, "i": 8}}, "signature": {"hash": {"$binary": {"base64": "rOfPFKpgglg5DigfTjeL2n1qgeg=", "subType": "00"}}, "keyId": 7329363855287517191}}, "operationTime": {"$timestamp": {"t": 1719546021, "i": 8}}}
at com.mongodb.internal.connection.ProtocolHelper.getCommandFailureException(ProtocolHelper.java:205) ~[mongodb-driver-core-4.11.2.jar:na]
at com.mongodb.internal.connection.InternalStreamConnection.receiveCommandMessageResponse(InternalStreamConnection.java:454) ~[mongodb-driver-core-4.11.2.jar:na]
at com.mongodb.internal.connection.InternalStreamConnection.sendAndReceive(InternalStreamConnection.java:372) ~[mongodb-driver-core-4.11.2.jar:na]
at com.mongodb.internal.connection.UsageTrackingInternalConnection.sendAndReceive(UsageTrackingInternalConnection.java:114) ~[mongodb-driver-core-4.11.2.jar:na]
at com.mongodb.internal.connection.DefaultConnectionPool$PooledConnection.sendAndReceive(DefaultConnectionPool.java:765) ~[mongodb-driver-core-4.11.2.jar:na]
</code>
<code>com.mongodb.MongoCommandException: Command failed with error 40234 (Location40234): 'The field 'idFields' must be an accumulator object' on server pl-1-us-west-2.c8foo.mongodb.net:1037. The full response is {"ok": 0.0, "errmsg": "The field 'idFields' must be an accumulator object", "code": 40234, "codeName": "Location40234", "$clusterTime": {"clusterTime": {"$timestamp": {"t": 1719546021, "i": 8}}, "signature": {"hash": {"$binary": {"base64": "rOfPFKpgglg5DigfTjeL2n1qgeg=", "subType": "00"}}, "keyId": 7329363855287517191}}, "operationTime": {"$timestamp": {"t": 1719546021, "i": 8}}}
at com.mongodb.internal.connection.ProtocolHelper.getCommandFailureException(ProtocolHelper.java:205) ~[mongodb-driver-core-4.11.2.jar:na]
at com.mongodb.internal.connection.InternalStreamConnection.receiveCommandMessageResponse(InternalStreamConnection.java:454) ~[mongodb-driver-core-4.11.2.jar:na]
at com.mongodb.internal.connection.InternalStreamConnection.sendAndReceive(InternalStreamConnection.java:372) ~[mongodb-driver-core-4.11.2.jar:na]
at com.mongodb.internal.connection.UsageTrackingInternalConnection.sendAndReceive(UsageTrackingInternalConnection.java:114) ~[mongodb-driver-core-4.11.2.jar:na]
at com.mongodb.internal.connection.DefaultConnectionPool$PooledConnection.sendAndReceive(DefaultConnectionPool.java:765) ~[mongodb-driver-core-4.11.2.jar:na]
</code>
com.mongodb.MongoCommandException: Command failed with error 40234 (Location40234): 'The field 'idFields' must be an accumulator object' on server pl-1-us-west-2.c8foo.mongodb.net:1037. The full response is {"ok": 0.0, "errmsg": "The field 'idFields' must be an accumulator object", "code": 40234, "codeName": "Location40234", "$clusterTime": {"clusterTime": {"$timestamp": {"t": 1719546021, "i": 8}}, "signature": {"hash": {"$binary": {"base64": "rOfPFKpgglg5DigfTjeL2n1qgeg=", "subType": "00"}}, "keyId": 7329363855287517191}}, "operationTime": {"$timestamp": {"t": 1719546021, "i": 8}}}
at com.mongodb.internal.connection.ProtocolHelper.getCommandFailureException(ProtocolHelper.java:205) ~[mongodb-driver-core-4.11.2.jar:na]
at com.mongodb.internal.connection.InternalStreamConnection.receiveCommandMessageResponse(InternalStreamConnection.java:454) ~[mongodb-driver-core-4.11.2.jar:na]
at com.mongodb.internal.connection.InternalStreamConnection.sendAndReceive(InternalStreamConnection.java:372) ~[mongodb-driver-core-4.11.2.jar:na]
at com.mongodb.internal.connection.UsageTrackingInternalConnection.sendAndReceive(UsageTrackingInternalConnection.java:114) ~[mongodb-driver-core-4.11.2.jar:na]
at com.mongodb.internal.connection.DefaultConnectionPool$PooledConnection.sendAndReceive(DefaultConnectionPool.java:765) ~[mongodb-driver-core-4.11.2.jar:na]
If I deserialize the pipeline, I get this query:
<code>[
{
"$search": {
...
}
},
{
"$group": {
"idFields": {
"originalFields": [
{
"synthetic": false,
"field": {
"raw": "listing.price.value",
"name": "price.value",
"target": "listing.price.value"
}
}
],
"syntheticFields": []
},
"operations": [
{
"op": "AVG",
"key": "avgPrice",
"reference": "$avg(listing.price.value)"
}
]
}
},
{
"$project": {
"avgPrice": 1
}
}
]
</code>
<code>[
{
"$search": {
...
}
},
{
"$group": {
"idFields": {
"originalFields": [
{
"synthetic": false,
"field": {
"raw": "listing.price.value",
"name": "price.value",
"target": "listing.price.value"
}
}
],
"syntheticFields": []
},
"operations": [
{
"op": "AVG",
"key": "avgPrice",
"reference": "$avg(listing.price.value)"
}
]
}
},
{
"$project": {
"avgPrice": 1
}
}
]
</code>
[
{
"$search": {
...
}
},
{
"$group": {
"idFields": {
"originalFields": [
{
"synthetic": false,
"field": {
"raw": "listing.price.value",
"name": "price.value",
"target": "listing.price.value"
}
}
],
"syntheticFields": []
},
"operations": [
{
"op": "AVG",
"key": "avgPrice",
"reference": "$avg(listing.price.value)"
}
]
}
},
{
"$project": {
"avgPrice": 1
}
}
]
I can hand write a group stage that does what I want:
<code> $group: {
_id: null,
avgPrice: { $avg: "$listing.price.value" }
}
</code>
<code> $group: {
_id: null,
avgPrice: { $avg: "$listing.price.value" }
}
</code>
$group: {
_id: null,
avgPrice: { $avg: "$listing.price.value" }
}
But the Spring Data serializes quite differently.
I have tried passing various field names to the group()
method but I basically get the same error.
Any help would be much appreciated.