I am using Java Springdata elasticsearch and I want to use sub-aggregation and model the following query.
{
"from" : 0,
"size" : 10,
"sort" : [ {
"_score" : {
"order" : "desc"
}
} ],
"aggregations" : {
"parentAgg" : {
"terms" : {
"field" : "parentField",
"size" : 0
},
"aggregations" : {
"childAgg" : {
"terms" : {
"field" : "childField"
}
}
}
}
}
}
Currently I have used subaggregation (i.e. Aggregation.subAggregation(subAggName)) however output I get is -
"aggregations": [
{
"field": "parentAgg",
"values": [
{
"term": "val1",
"docCount": 2
},
{
"term": "val2",
"docCount": 2
},
{
"term": "val3",
"docCount": 1
}
]
}
]
Relavent Java Code -
for (Object aggregationField : request.getAggregationFields()) {
TermsBuilder termBuilder = AggregationBuilders.terms(aggregationField.toString())
.field(aggregationField.toString()).size(0);
if(aggregationField.toString().equals("parentField"))
{
TermsBuilder childBuilder = AggregationBuilders.terms("childAgg").field("childField").size(0);
termBuilder.subAggregation(childBuilder);
}
nativeSearchQueryBuilder.addAggregation(termBuilder);
}
Can you please let me know what I am missing?
Related
I queried elasticsearch use aggregation. My response is
"aggregations" : {
"time" : {
"buckets" : [
{
"key_as_string" : "2019-01-01",
"key" : 1546300800000,
"doc_count" : 12,
"look_up" : {
"doc_count" : 5,
"unlock_not_suggested" : {
"doc_count" : 2
}
}
},
. Now I want to use script to transform data like this
['lookup': 5, 'unlock_not_suggested': 2 ]
I find and see an advice use script like this but I don't know how to add this in my query:
aggs.stream().flatMap(a -> a.stream().flatMap( b->b.stream().map( c-> -> ['A': a.key, 'B': b.key, 'C': c.key ] ) ) ).collect(Collectors.toList())
My query is
POST test.userstats/_search
{
"size": 0,
"aggs": {
"time": {
"date_histogram": {
"field": "event_stats.watch_10_percent_video.first_time",
"format": "yyyy-MM-dd",
"interval": "year",
"min_doc_count": 0
},
"aggs": {
"look_up": {
"filter": {
"exists": {
"field": "event_stats.look_up"
}
},
"aggs": {
"unlock_not_suggested": {
"filter": {
"exists": {
"field": "event_stats.unlock_not_suggested_video"
}
}
}
}
}
}
}
}
}
I have this collection of documents:
[
{
"name": "name1",
"data": [
{
"numbers": ["1","2","3"]
}
]
},
{
"name": "name2",
"data": [
{
"numbers": ["2","5","3"]
}
]
},
{
"name": "name3",
"data": [
{
"numbers": ["1","5","2"]
}
]
},
{
"name": "name4",
"data": [
{
"numbers": ["1","4","3"]
}
]
},
{
"name": "name5",
"data": [
{
"numbers": ["1","2"]
}
]
}
]
I want to get all documents of this collection when an array passed as a parameter is a subset of data.numbers.
This is the aggregation that I'm using.
db.testing.aggregate(
[
{ "$match" : { "data.numbers" : { "$exists" : true } } },
{ "$project" : { "is_subset" : { "$filter" : { "input" : "$data", "as" : "d", "cond" : { "$setIsSubset" :[ ["1"],"$$d.numbers"] } } } } },
{ "$match" : { "is_subset.0" : { "$exists" : true } } }]
);
I'm trying to reproduce the above aggregation in Spring Data MongoDB.
How to pass an array as parameter in $filter and $setIsSubset functions?
operations.aggregate(
newAggregation(Testing.class,
match(where("data.numbers").exists(true)),
project().and(
filter("data")
.as("d")
.by(???))
.as("is_subset"),
match(where("is_subset.0").exists(true))
), Testing.class);
I solve my issue.
operations.aggregate(
newAggregation(Testing.class,
match(where("data.numbers").exists(true)),
project("id", "name").and(
filter("data")
.as("d")
.by(context -> new Document("$setIsSubset", Arrays.asList(numbers, "$$d.numbers"))))
.as("is_subset"),
match(where("is_subset.0").exists(true))
), Testing.class);
I created a Document with the content that I needed in the $filter condition.
new Document("$setIsSubset", Arrays.asList(numbers, "$$d.numbers"))
I have the following data structure
[{
"id": "1c7bbebd-bc3d-4352-9ac0-98c01d13189d",
"version": 0,
"groups": [
{
"internalName": "Admin group",
"fields": [
{
"internalName": "Is verified",
"uiProperties": {
"isShow": true
}
},
{
"internalName": "Hide",
"uiProperties": {
"isHide": false
}
},
...
]
},
...
]
},
{
"id": "2b7bbebd-bc3d-4352-9ac0-98c01d13189d",
"version": 0,
"groups": [
{
"internalName": "User group",
"fields": [
{
"internalName": "Is verified",
"uiProperties": {
"isShow": true
}
},
{
"internalName": "Blocked",
"uiProperties": {
"isBlocked": true
}
},
...
]
},
...
]
},
...
]
Internal names of the fields can be repeated. I want to group by group.field.internalName and cut the array(for pagination) and get the output like:
{
"totalCount": 3,
"items": [
{
"internalName": "Blocked"
},
{
"internalName": "Hide"
},
{
"internalName": "Is verified"
}
]}
I wrote a query that works,
db.layouts.aggregate(
{
$unwind : "$groups"
},
{
$unwind : "$groups.fields"
},
{
$group: {
"_id" : {
"internalName" : "$groups.fields.internalName",
},
"internalName" : {
$first : "$groups.fields.internalName"
}
}
},
{
$group: {
"_id" : null,
"items" : {
$push : "$$ROOT"
},
"totalCount" : {
$sum : 1
}
}
},
{
$project: {
"items" : {
$slice : [ "$items", 0, 20 ]
},
"totalCount": 1
}
})
but I have the problem of translating it to java api. Notice that i need to use mongoTemplate approach. Here is what i have and where i'm struck
final List<AggregationOperation> aggregationOperations = new ArrayList<>();
aggregationOperations.add(unwind("groups"));
aggregationOperations.add(unwind("groups.fields"));
aggregationOperations.add(
group("groups.fields.internalName")
.first("groups.fields.internalName").as("internalName")
);
aggregationOperations.add(
group()
.push("$$ROOT").as("fields")
.sum("1").as("totalCount") // ERROR only string ref can be placed, but i need a number?
);
aggregationOperations.add(
project()
.andInclude("totalCount")
.and("fields").slice(size, page * size)
);
final Aggregation aggregation = newAggregation(aggregationOperations);
mongoTemplate.aggregate(aggregation, LAYOUTS, FieldLites.class).getMappedResults()
With this query i have the problem with sum(), because i can place only a String ref by api(but need a number) and with project operation - got an exception
java.lang.IllegalArgumentException: Invalid reference 'totalCount'!] with root cause
Can you help me with this query translation?
You can use count
group()
.push("$$ROOT").as("fields")
.count().as("totalCount")
I am facing difficulty in retrieving the nested document object of another nested list. Please help me to resolve the same. My mongoDB document is as follows:
{
"_id" : "PT5",
"departmentId" : "DEPT5",
"subDepartmentList" : [
{
"subDepartmentId" : "SUBDEPT19",
"subDepartmentName" : "X-Ray",
"labServiceList" : [
{
"_id" : "123abc",
"subDepartmentId" : "SUBDEPT19",
"labServiceName" : "serviceOne"
},
{
"_id" : "123def",
"subDepartmentId" : "SUBDEPT19",
"labServiceName" : "hello",
}
]
},
{
"subDepartmentId" : "SUBDEPT21",
"subDepartmentName" : "Haemotology",
"labServiceList" : [
{
"_id" : "456abc",
"subDepartmentId" : "SUBDEPT21",
"labServiceName" : "abcd",
}
]
}
]
}
From the above document I want to retrieve only one object of labServiceList by using its _id value(Ex: "_id" : "123abc" in this document). And I don't want to get any other fields apart from the matching nested document. I have tried with the below query:
db.labServiceMasters.aggregate([
{"$project": {
"subDepartmentList": {"$filter": {
"input": '$subDepartmentList.labServiceList',
"as": 'labServiceList',
"cond": {"$eq": ['$$labServiceList._id', '123abc']}
}},
"_id": 0
}}
])
Also I have tried using $map operator, but nothing goes in my way. Please help me to resolve this problem. And also please help me to write the query for the same using mongoTemplate in Java. Any suggestions would be appreciable. Thanks in advance :-)
You actually need to nest a $map inside the $filter and another $filter inside the $map. And use $arrayElemAt to get the single entries:
db.labServiceMasters.aggregate([
{ "$project": {
"subDepartmentList": {
"$arrayElemAt": [
{ "$filter": {
"input": {
"$map": {
"input": "$subDepartmentList",
"as": "sd",
"in": {
"$arrayElemAt": [
{ "$filter": {
"input": "$$sd.labServiceList",
"as": "ls",
"cond": { "$eq": [ "$$ls._id", "123abc" ] }
}},
0
]
}
}
},
"as": "sd",
"cond": { "$ne": [ "$$sd", null ] }
}},
0
]
}
}}
])
Returns:
{
"_id" : "PT5",
"subDepartmentList" : {
"_id" : "123abc",
"subDepartmentId" : "SUBDEPT19",
"labServiceName" : "serviceOne"
}
}
Which for spring-mongodb is:
Aggregation aggregation = newAggregation(
project("subDepartmentList").and(new AggregationExpression() {
#Override
public DBObject toDbObject(AggregationOperationContext context) {
return new BasicDBObject(
"$arrayElemAt", Arrays.asList(
new BasicDBObject("$filter",
new BasicDBObject("input",
new BasicDBObject("$map",
new BasicDBObject("input","$subDepartmentList")
.append("as","sd")
.append("in",new BasicDBObject(
"$arrayElemAt", Arrays.asList(
new BasicDBObject("$filter",
new BasicDBObject("input","$$sd.labServiceList")
.append("as","ls")
.append("cond", new BasicDBObject("$eq", Arrays.asList("$$ls._id","123abc")))
),
0
)
))
)
)
.append("as","sd")
.append("$ne", Arrays.asList("$$sd", null))
),
0
)
);
}
}).as("subDepartmentList")
);
And serializes the same:
{
"aggregate": "labServiceMasters",
"pipeline": [
{
"$project": {
"subDepartmentList": {
"$arrayElemAt": [
{
"$filter": {
"input": {
"$map": {
"input": "$subDepartmentList",
"as": "sd",
"in": {
"$arrayElemAt": [
{
"$filter": {
"input": "$$sd.labServiceList",
"as": "ls",
"cond": {
"$eq": [
"$$ls._id",
"123abc"
]
}
}
},
0.0
]
}
}
},
"as": "sd",
"$ne": [
"$$sd",
null
]
}
},
0.0
]
}
}
}
]
}
I need to export customer records from database of mongoDB. Exported customer records should not have duplicated values. "firstName+lastName+code" is the key to DE-duped the record and If there are two records present in database with same key then I need to give preference to source field with value other than email.
customer (id,firstName,lastName,code,source) collection is this.
If there are record 3 records with same unique key and 3 different sources then i need to choose only one record between 2 sources(TV,internet){or if there are n number of sources i need the one record only}not with the 'email'(as email will be choosen when only one record is present with the unique key and source is email)
query using:
db.customer.aggregate([
{
"$match": {
"active": true,
"dealerCode": { "$in": ["111391"] },
"source": { "$in": ["email", "TV", "internet"] }
}
},
{
$group: {
"_id": {
"firstName": "$personalInfo.firstName",
"lastName": "$personalInfo.lastName",
"code": "$vehicle.code"
},
"source": {
$addToSet: { "source": "$source" }
}
}
},
{
$redact:
{
$cond: [
{ $eq: [{ $ifNull: ["$source", "other"] }, "email"] },
"$$PRUNE",
"$$DESCEND"
]
}
},
{
$project:
{
"source":
{
$map:
{
"input": {
$cond: [
{ $eq: [{ $size: "$source" }, 0] },
[{ "source": "email" }],
"$source"
]
},
"as": "inp",
"in": "$$inp.source"
}
},
"record": { "_id": 1 }
}
}
])
sample output:
{ "_id" : { "firstName" : "sGI6YaJ36WRfI4xuJQzI7A==", "lastName" : "99eQ7i+uTOqO8X+IPW+NOA==", "code" : "1GTHK23688F113955" }, "source" : ["internet"] }
{ "_id" : { "firstName" : "WYDROTF/9vs9O7XhdIKd5Q==", "lastName" : "BM18Uq/ltcbdx0UJOXh7Sw==", "code" : "1G4GE5GV5AF180133" }, "source" : ["internet"] }
{ "_id" : { "firstName" : "id+U2gYNHQaNQRWXpe34MA==", "lastName" : "AIs1G33QnH9RB0nupJEvjw==", "code" : "1G4GE5EV0AF177966" }, "source" : ["internet"] }
{ "_id" : { "firstName" : "qhreJVuUA5l8lnBPVhMAdw==", "lastName" : "petb0Qx3YPfebSioY0wL9w==", "code" : "1G1AL55F277253143" }, "source" : ["TV"] }
{ "_id" : { "firstName" : "qhreJVuUA5l8lnBPVhMAdw==", "lastName" : "6LB/NmhbfqTagbOnHFGoog==", "code" : "1GCVKREC0EZ168134" }, "source" : ["TV", "internet"] }
This is a problem with this query please suggest :(
Your code doesn't work, because $cond is not an accumulator operator. Only these accumulator operators, can be used in a $group stage.
Assuming your records contain not more than two possible values of source as you mention in your question, you could add a conditional $project stage and modify the $group stage as,
Code:
db.customer.aggregate([
{
$group: {
"_id": {
"id": "$id",
"firstName": "$firstName",
"lastName": "$lastName",
"code": "$code"
},
"sourceA": { $first: "$source" },
"sourceB": { $last: "$source" }
}
},
{
$project: {
"source": {
$cond: [
{ $eq: ["$sourceA", "email"] },
"$sourceB",
"$sourceA"
]
}
}
}
])
In case there can be more that two possible values for source, then you could do the following:
Group by the id, firstName, lastName and code. Accumulate
the unique values of source, using the $addToSet operator.
Use $redact to keep only the values other than email.
Project the required fields, if the source array is empty(all the elements have been removed), add a
value email to it.
Unwind the source field to list it as a field and not an array.
(optional)
Code:
db.customer.aggregate([
{
$group: {
"_id": {
"id": "$id",
"firstName": "$firstName",
"lastName": "$lastName",
"code": "$code"
},
"sourceArr": { $addToSet: { "source": "$source" } }
}
},
{
$redact: {
$cond: [
{ $eq: [{ $ifNull: ["$source", "other"] }, "email"] },
"$$PRUNE",
"$$DESCEND"
]
}
},
{
$project: {
"source": {
$map: {
"input":
{
$cond: [
{ $eq: [{ $size: "$sourceArr" }, 0] },
[{ "source": "item" }],
"$sourceArr"]
},
"as": "inp",
"in": "$$inp.source"
}
}
}
}
])