Group and count MongoDB java spring aggregation - java

What is the best approach, giving more of these documents in my Mongo collection, to return a result that for each name in Tokio city contains the number of each name has logged via Google and Facebook, and how many times gived the consent as true or false ?
class User{
private String name;
private String city;
private String login; //can be only Google or Facebook
private Boolean consent // true or false
}
Result can be an array of these object like :
{
"name": "Paul",
"registration": {
"Google": 55,
"Facebook": 22
},
"consent": {
"true": 6,
"false": 1
}
}
And I'm approaching with :
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(Criteria.where("city").is("Tokio")),
Aggregation.group // how build here
);
AggregationResults<Result> res = mongoTemplate.aggregate(aggregation, "event", Result.class);
What's the best way to make group and count?

Related

get list from DB and merge the common fields of the list

I have a list as follows:-
List<ScheduleActionDispatchDTO> pendingScheduleActions
=restClientApi.getPendingSchedules();
The above list is coming from DB whose values are like this -
schedule_request_id = 576, user_id = 24,
start_time_utc = '2022-12-16 21:00:00', end_time_utc = '2022-12-17 01:00:00',
request_json = '{"testId": "5", "grade": "A"}'
schedule_request_id = 576, user_id = 24,
start_time_utc = '2022-12-16 21:00:00', end_time_utc = '2022-12-17 01:00:00',
request_json = '{"subjectId": "10", "name": "dictation"}'
schedule_request_id = 577, user_id = 24, start_time_utc = '2022-12-17 21:00:00',
end_time_utc = '2022-12-18 01:00:00', request_json = '{"testId": "5", "grade": "A"}'
Now I want the result to be such that if values of schedule_request_id, user_id, start_time_utc and end_time_utc of any rows are same then merge the values of request_json of those rows together.
So it should become -
schedule_request_id = 576, user_id = 24,
start_time_utc = '2022-12-16 21:00:00', end_time_utc = '2022-12-17 01:00:00',
combinedResult = '[{"testId": "5", "grade": "A"}, {"subjectId": "10", "name": "dictation"}]'
and
schedule_request_id = 577, user_id = 24, start_time_utc = '2022-12-17 21:00:00',
end_time_utc = '2022-12-18 01:00:00', combinedResult = '{"testId": "5", "grade": "A"}'
I tried this -
Map<Long, List<ScheduleActionDispatchDTO>> requestMap = pendingScheduleActions.stream()
.collect(Collectors.groupingBy(
ScheduleActionDispatchDTO::getScheduleRequestId, Collectors.toList()));
for (Map.Entry<Long, List<ScheduleActionDispatchDTO>> entry : requestMap.entrySet()) {
List<ScheduleActionDispatchDTO> sameRequestActions = entry.getValue();
Map<ScheduleActionDispatchPair, ScheduleActionDispatchDTO> schedulePairAction =
sameRequestActions.stream().
collect(Collectors.toMap(
s -> new ScheduleActionDispatchPair(s.getScheduleRequestId(), s.getUserUd(), s.getStartTimeUtc(), s.getEndTimeUtc()),
s -> s));
// iterate and combine but not sure how
}
Well, you could do the following. I assumed you have a record1 of the following form:
public record PendingScheduleAction(
long scheduleRequestId,
int userId,
LocalDateTime startTimeUtc,
LocalDateTime endTimeUtc,
String requestJson
) { }
We could first create a Merger, which is used as grouping-by key. We add an ofPendingScheduleAction method, which is a convenience method to ease the grouping-by. We also add a merge method, which is able to merge two PendingScheduleAction objects.
record Merger(long scheduleRequestId, int userId, LocalDateTime startTimeUtc, LocalDateTime endTimeUtc) {
public static Merger ofPendingScheduleAction(PendingScheduleAction action) {
return new Merger(action.scheduleRequestId(), action.userId(), action.startTimeUtc(), action.endTimeUtc());
}
private static PendingScheduleAction merge(PendingScheduleAction a, PendingScheduleAction b) {
String json = a.requestJson() + '\0' + b.requestJson();
return new PendingScheduleAction(a.scheduleRequestId(), a.userId(), a.startTimeUtc(), a.endTimeUtc(), json);
}
}
Now you can utilize groupingBy, reducing and collectingAndThen in order to achieve the desired result:
list.stream()
.collect(groupingBy(Merger::ofPendingScheduleAction, collectingAndThen(reducing(Merger::merge), optional -> {
var entry = optional.orElseThrow();
String[] jsonLines = entry.requestJson().split("\0");
String json = (jsonLines.length == 1 ? jsonLines[0] : "[" + String.join(",", jsonLines) + "]");
return new PendingScheduleAction(entry.scheduleRequestId(), entry.userId(), entry.startTimeUtc(), entry.endTimeUtc(), json);
})));
What happens here, is that we first define how our PendingScheduleAction instances are grouped. The Javadocs call this the classifier. Now you said "such that if values of schedule_request_id, user_id, start_time_utc and end_time_utc of any rows are same then merge", so we need to have those properties together into an object. The Merger class does this.
Then Collectors.reducing(Merger::merge) takes all objects of a group and merges them using the Merger::merge method. Since reducing returns an Optional, we need to unpack it (with orElseThrow), but we also need to fix the JSON, since we temporarily joined it with a NUL byte.
Then the result is a Map<Merger, PendingScheduleAction>, of which you can call values() to get a list with the merged values.
A few notes:
I used a NUL byte to temporarily separate different JSON parts (which is kinda hacky). This is due to your requirement that you want multiple merged JSON entries to be in a JSON array, but a single entry without an array. I suggest you put all JSON entries into an array, regardless of the number of entries (which may be 1). This is both easier to parse and easier to process.
You could replace all \0s by , in the abovementioned code and the finisher function of the collectingAndThen method would look like this:
var entry = optional.orElseThrow();
return new PendingScheduleAction(entry.scheduleRequestId(), entry.userId(), entry.startTimeUtc(), entry.endTimeUtc(), "[" + entry.requestJson() + "]");
Your start time and end time suggest they are UTC timestamps, but the date strings show a local time. I think your timestamps should look like something like this: 2022-12-16T21:00:00Z.
1 If you don't have access to the records feature, you could easily simulate them with the following:
public class PendingScheduleAction {
private long scheduleRequestId;
private int userId;
private LocalDateTime startTimeUtc;
private LocalDateTime endTimeUtc;
private String requestJson;
// Canonical all-args constructor
public PendingScheduleAction(long scheduleRequestId, int userId, LocalDateTime startTimeUtc, LocalDateTime endTimeUtc, String requestJson) {
this.scheduleRequestId = scheduleRequestId;
this.userId = userId;
this.startTimeUtc = startTimeUtc;
this.endTimeUtc = endTimeUtc;
this.requestJson = requestJson;
}
// Record-style getter (just the name of the field, not starting with 'get'
public long scheduleRequestId() {
return scheduleRequestId;
}
// And the remaining getters
public boolean equals(Object o) {
// A proper equals implementation.
// Don't forget hashCode()
}
}
You could also use Lombok to annotate the class with #AllArgsConstructor, #Getter and #EqualsAndHashCode.

How to get Page as result in Querydsl query with fetch or fetchResults properly?

Hi what i trying to achieve here is, i want to submit Pageable data into QueryDsl query and get the result as Page, how can i do it properly? here is what i do until now :
here is my controller :
#PostMapping("/view-latest-stock-by-product-codes")
public ResponseEntity<RequestResponseDTO<Page<StockAkhirResponseDto>>> findStockByProductCodes(
#RequestBody StockViewByProductCodesDto request) {
Page<StockAkhirResponseDto> stockAkhir = stockService.findByBulkProduct(request);
return ResponseEntity.ok(new RequestResponseDTO<>(PESAN_TAMPIL_BERHASIL, stockAkhir));
}
in my controller i submit StockViewByProductCodesDto which is looked like this :
#Data
public class StockViewByProductCodesDto implements Serializable {
private static final long serialVersionUID = -2530161364843162467L;
#Schema(description = "Kode gudang yang ingin di tampilkan", example = "GBKTJKT1", required = true)
private String warehouseCode;
#Schema(description = "id dari sebuah branch", example = "1", required = true)
private Long branchId;
#Schema(description = "Kode Branch", example = "JKT", required = true)
private String branchCode;
#Schema(description = "Kode Product yang merupakan kode yang di ambil dari master product", example = "[\"MCM-508\",\"TL-101\"]", required = true)
private List<String> productCodes;
#Schema(description = "Size of row per page", example = "15", required = true)
#NotNull
private int size;
#Schema(description = "Page number", example = "1", required = true)
#NotNull
private int page;
#Schema(description = "Sort by", example = "id", required = false)
private String sort;
}
and here is my service :
public Page<StockAkhirResponseDto> findByBulkProduct(StockViewByProductCodesDto request) {
String warehouseCode = request.getWarehouseCode();
Long branchId = request.getBranchId();
String branchCode = request.getBranchCode();
List<String> productCodes = request.getProductCodes();
Set<String> productCodesSet = new HashSet<String>(productCodes);
Pageable pageable = PageUtils.pageableUtils(request);
Page<StockAkhirResponseDto> stockAkhir = iStockQdslRepository.findBulkStockAkhirPage(warehouseCode, branchId, branchCode, productCodesSet, pageable);
return stockAkhir;
}
as you can see, i extract pageable information with PageUtils.pageableUtils(request), here is my pageableUtils function looked like :
public static Pageable pageableUtils(RequestKeyword request) {
int page = 0;
int size = 20;
if (request.getPage() > 0) {
page = request.getPage() - 1;
}
if (request.getSize() > 0) {
size = request.getSize();
}
if (!request.getSort().isEmpty()) {
return PageRequest.of(page, size, Sort.by(request.getSort()).descending());
} else {
return PageRequest.of(page, size);
}
}
after i got the Pageable data, i submit it into my repository, which is looked like this :
public Page<StockAkhirResponseDto> findBulkStockAkhirPage(String warehouseCode, Long branchId, String branchCode,
Set<String> productCodes, Pageable pageable) {
JPQLQuery<Tuple> query = new JPAQuery<>(em);
long offset = pageable.getOffset();
long limit = pageable.getPageSize();
QStock qStock = QStock.stock;
NumberExpression<Integer> totalQty = qStock.qty.sum().intValue();
query = query.select(qStock.productId, qStock.productCode, totalQty).from(qStock)
.where(qStock.warehouseCode.eq(warehouseCode), qStock.productCode.in(productCodes),
qStock.branchCode.eq(branchCode), qStock.branchId.eq(branchId))
.groupBy(qStock.productId, qStock.productCode);
query.limit(limit);
query.offset(offset);
QueryResults<Tuple> result = query.fetchResults();
long total = result.getTotal();
List<Tuple> rows = result.getResults();
List<StockAkhirResponseDto> stockAkhirDto = rows.stream()
.map(t -> new StockAkhirResponseDto(t.get(0, Long.class), t.get(1, String.class), t.get(2, Integer.class)))
.collect(Collectors.toList());
return new PageImpl<>(stockAkhirDto, pageable, total);
}
there is no error in my editor when viewing this my repository and i able to run my project, but when i execute my repository function, i got this error :
"org.hibernate.hql.internal.ast.QuerySyntaxException: expecting CLOSE,
found ',' near line 1, column 38 [select count(distinct
stock.productId, stock.productCode, stock.warehouseId,
stock.warehouseCode, stock.branchCode, stock.branchId)\nfrom
com.bit.microservices.b2b.warehouse.entity.Stock stock\nwhere
stock.warehouseCode = ?1 and stock.productCode in ?2 and
stock.branchCode = ?3 and stock.branchId = ?4]; nested exception is
java.lang.IllegalArgumentException:
org.hibernate.hql.internal.ast.QuerySyntaxException: expecting CLOSE,
found ',' near line 1, column 38 [select count(distinct
stock.productId, stock.productCode, stock.warehouseId,
stock.warehouseCode, stock.branchCode, stock.branchId)\nfrom
com.bit.microservices.b2b.warehouse.entity.Stock stock\nwhere
stock.warehouseCode = ?1 and stock.productCode in ?2 and
stock.branchCode = ?3 and stock.branchId = ?4]"
the problem is here, on this line :
QueryResults<Tuple> result = query.fetchResults();
when i execute that line, it give me that error, i try to get the fetchResult, because i want to get the .getTotal() for the total.
but if i execute the query with .fetch(), it worked fine, like this :
List<StockAkhirResponseDto> stockAkhirDto = query.fetch()
i got my sql result execute correctly, what did i missed here? how do i get Page result correctly?
Your problem could be related with an open QueryDSL issue. The documented issue has to do with the use of fetchCount but I think very likely could be also your case.
Consider the following comment in the mentioned issue:
fetchCount() uses a COUNT function, which is an aggregate function. Your query already has aggregate functions. You cant aggregate aggregate functions, unless a subquery is used (which is not available in JPA). Therefore this use case cannot be supported.
The issue also provides a temporary solution.
Basically, the idea is be able to perform the COUNT by creating a statement over the initial select. AFAIK it is not possible with QueryDsl and this is why in the indicated workarounds they access the underline mechanisms provided by Hibernate.
Perhaps, another thing that you can try to avoid the limitation is to create a database view for your query, the corresponding QueryDsl objects over it, and use these objects to perform the actual computation. I am aware that it is not an ideal solution, but it will bypass this current QueryDsl limitation.

How to map a value in an nested array to a property

I am trying to parse a json and get hold of value inside an array -
{
"hits": {
"hits": [
{
"name": [
{
"family": "Doe",
"given": "Jon",
"middle": "Smith",
"use": "Commercial"
}
]
}
]
}
}
I realized that i have a one more array at root level so i decided to parse hits which is a list and then name which is another list. I am able to see entire name array in the response but instead i would rather want to see only given value that is mapped to fstNm
private String fstNm;
private List<Map<String,Object>> name;
public String getFstNm() { return fstNm; }
#JsonProperty("hits")
public void setFstNm(List<Map<String, Object>> hits) {
name = (List) hits.get(0).get("name");
this.fstNm= (String) name.get(0).get("given");
}
expected output-
{
"fstNm": "Jon"
}
I would appreciate any help here.
Just change the setFstNm() method as below
#JsonProperty("hits")
public void setFstNm(Map<String, List<Map<String, Object>>> hits) {
name = (List) hits.get("hits").get(0).get("name");
this.fstNm = (String) name.get(0).get("given");
}
Not sure why you have to have name be the same object type as hits
private List<Map<String,Object>> name;
I think you can accomplish getting all the values of name by declaring it as
`ArrayList<String> name;`
From there you can try to set name something like
ArrayList<String> name = (List) hits.get(0).get("name");
Then you can create a get method to get name and then access it in your setter like
ArrayList<String> name = getName();
fstNm = name.get(2);

Sorting based on Date in Java SpringBoot

Here is my API Service , i need to get a list of calculation using an api but sort it based on descending order of Date, with the current implementation Its ascending
/*
* Get Risk Evaluation based on id
*/
public List<RiskEvaluation> getByTest(String test) {
return riskEvaluationRepository.findByTest(test);
}
The controller is
#GetMapping("/{test}")
#ApiOperation(value = "gets", notes = "Get all the Risk Assessments based on TEST.", response = RiskEvaluation.class, tags = {"GetsAllRisksforaTEST",})
public List<RiskEvaluation> getByTest(#PathVariable("test") String test) {
return scoringService.getByTest(test);
}
the current output is getting with this API is
{
"accountID": null,
"bap_id": "cccccccccc",
"release_version": "1.9",
"risk_level": "Medium",
"risk_score": 2,
"createdOn": 1533584332466,
"updatedOn": 1533584332466,
"id": "8340f05c-06c4-430a-bbbd-a53d0ce60dea"
},
{
"accountID": null,
"bap_id": "cccccccccc",
"release_version": "1.9",
"risk_level": "Medium",
"risk_score": 2,
"createdOn": 1533584551115,
"updatedOn": 1533584551115,
"id": "ae175b92-805d-46b5-8f67-95795e232057"
},
{
"accountID": null,
"bap_id": "cccccccccc",
"release_version": "2.0",
"risk_level": "Medium",
"risk_score": 2,
"createdOn": 1533584718584,
"updatedOn": 1533584718584,
"id": "d19177ef-b8c4-4951-bbdb-4e4d7274be81"
}
How can I sort it based on the value createdOn
If you are using JpaRepository, you can pass a org.springframework.data.domain.Pageable object with the page, size and the sort order you want.
For example:
int page = 0, size = 10;
Pageable pageable = PageRequest.of(page, size, new Sort("createdOn", Sort.Direction.DESC));
...
Hope it works!
Into you scoringService, do you have a method that's calling a RiskEvaluation repository, right? Probably to do it, its being used a JpaRepository or CrudRepository, in anyone if it has a findAll, you can use:
#Query("SELECT r FROM RiskEvaluation r ORDER BY createdOn ASC")
List<RiskEvaluation> findAll();
Or
List<RiskEvaluation> findAllOrderByCreatedOnAsc();
You can see the examples in https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.details.
interface PersonRepository extends Repository<User, Long> {
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// Enables the distinct flag for the query
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
// Enabling ignoring case for an individual property
List<Person> findByLastnameIgnoreCase(String lastname);
// Enabling ignoring case for all suitable properties
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
// Enabling static ORDER BY for a query
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}
Pageable pageable = PageRequest.of(0, 10, new Sort(Sort.Direction.DESC, "createdOn"));

Parsing for a dynamic json

I have json which I am trying to parse using Jackson. JSON looks as -
coupons: {
1: {
title: "Mode von",
description: "",
type: "offer",
code: "-",
expiry: "0000-00-00",
link: ""
},
2: {
title: "Prime 1",
description: "",
type: "offer",
code: "-",
expiry: "0000-00-00",
link: "http://test.com/"
}
}
The number of coupons are not constant here and would vary from response to response.
My dilemma is to create corresponding java class which could hold such object.
I tried with Map as -
public class Coupons {
Map<String, String>coupons = new HashMap<String, String>();
public Map<String, String> getCoupons() {
return coupons;
}
}
But -
System.out.println(coupons.getCoupons().get("type"));
System.out.println(coupons.getCoupons().get("code"));
always get me null. What would be right java class for this json?
your first level of keys are the index numbers 1, 2, 3, etc.
so in order to get the type and code, you have to specificy the key.
you could do this:
var coupons = coupons.getCoupons(); //<--breakpoint to see if this is really populated.
foreach( String key in coupons.Keys ){ //<- pseudo code, iterate over keys
var obj = coupons.get(key);
var type = obj.get("type");
etc..
}
hope this helps you move on

Categories