querydsl 에서 count, sum 함수 사용하기

트곰·2022년 4월 13일
0

newproject

목록 보기
11/13

어제 순항중이다가, sum 함수에서 난관을 맞이했다.....
매출 단건 조회의 경우, dto로 받아서 뿌려주면 그만인데
여러 건의 매출 조회는 몇 건인지, 총 얼마의 매출이 발생했는지 약환자와 침환자의 경우에 대해서도 각각 건수와 매출액 집계가 필요했다.

그러면 단 건 조회의 dto을 list로 받아오고
그 외의 값을 가지고 와야하는데, 한 번에 db에서 모든 값을 계산해서 가지고 오는 것이 성능적으로 좋을 것 같다는 생각이 들었다.

select count(case when is_acupuncture = 1 then 0 end) as acupunctureCase,
count(case when is_acupuncture = 0 then 0 end) as medicineCase,
count(*) as totalCase,
sum(if(is_acupuncture = 1, amount, 0)) as acupunctureAmount,
sum(if(is_acupuncture = 0, amount, 0)) as medicineAmount,
sum(amount) as totalAmount
from income
where income.patient_id = 4;

우선 MySQLWorkbench에서 쿼리문을 작성해서 날려보고 이를 적용해서 repository에 메소드를 생성하려고 한다.

총합이나 전체 카운트의 경우, 어렵지 않았다.
그리고, 조건을 걸어서 카운트 하는 경우도 어렵지 않았지만
조건을 걸어서 합을 구하는 것은 해결하지 못하고 있다..!

구글링을 아무리 해봐도 유사한 경우도 나오지 않는다ㅠㅠㅠ
합계를 구해야하는 컬럼과 조건을 체크해야하는 컬럼이 다르기 때문에 문제가 발생하는 것인데
해결되기 전에 미리 정리해두는 것이 좋을 것 같아 기록중..🫠

  1. 메소드를 두 개 혹은 세 개로 쪼개서, count하는 메소드와 매출액 합계를 내는 메소드를 만들고
    나중에 이를 합치는 방법도 있다.
    하지만, 이런 식으로 만들 경우 불필요하게 db에 들락날락 거리는 느낌이 든다.

  2. 원래 설계대로 테이블을 약 환자와 침 환자를 다시 나누고 집계하는 방식도 있을 것 같다.
    오히려 join으로는 sum을 구하기 쉬울 것 같고, 단순히 합계의 경우 각각 구한 값을 dto단에서 더해서 리턴해줄 수 도 있을 것 같다.
    하지만, 미수금 또는 결제 수단에 따른 합계를 계산한다고 하면 결국 똑같은 문제가 발생할 것 같다.
    (각 테이블 내에서 카드결제 집계, 현금결제 집계를 하면 동일한 문제 직면)


CaseBuilder와 이용해서 메소드를 만들었는데, InvalidDataAccessApiUsageException가 발생했다.

ExpressionUtils를 이용해서 만든 메소드는 값이 제대로 추출되지 않는다.

너무 이 기능에 많은 시간을 할애하고 있어서
우회해서 처리하는 방식을 생각해보니, 어차피 List로 가지고 오는 값이 있으니까
그 값을 stream으로 더하거나 카운트할 수 있다는 생각이 들었다.

@RequiredArgsConstructor
@Getter
@Builder
public class IncomeSummeryResponseDTO {

    private final long totalCase;
    private final long acupunctureCase;
    private final long medicineCase;

    private final long totalAmount;
    private final long acupunctureAmount;
    private final long medicineAmount;

    private final List<IncomeResponseDTO> incomeResponseDTOS;

    public static IncomeSummeryResponseDTO from(List<IncomeResponseDTO> incomeResponseDTOS) {

        long medicineAmount = incomeResponseDTOS.stream()
                .filter(incomeResponseDTO -> incomeResponseDTO.getIsAcupuncture() == false)
                .mapToLong(incomeResponseDTO -> incomeResponseDTO.getAmount())
                .sum();
        long acupunctureAmount = incomeResponseDTOS.stream()
                .filter(incomeResponseDTO -> incomeResponseDTO.getIsAcupuncture() == true)
                .mapToLong(incomeResponseDTO -> incomeResponseDTO.getAmount())
                .sum();
        long medicineCase = incomeResponseDTOS.stream()
                .filter(incomeResponseDTO -> incomeResponseDTO.getIsAcupuncture() == false)
                .count();

        long acupunctureCase = incomeResponseDTOS.stream()
                .filter(incomeResponseDTO -> incomeResponseDTO.getIsAcupuncture() == true)
                .count();

        return IncomeSummeryResponseDTO.builder()
                .incomeResponseDTOS(incomeResponseDTOS)
                .medicineAmount(medicineAmount)
                .acupunctureAmount(acupunctureAmount)
                .totalAmount(medicineAmount + acupunctureAmount)
                .medicineCase(medicineCase)
                .acupunctureCase(acupunctureCase)
                .totalCase(medicineCase + acupunctureCase)
                .build();
    }
}

List로 가지고 온 값이니까, DB에는 다시 들리지 않아도 되지만
찜찜한 기분이 든다....😭😭😭

stream으로 연산하는 것과 repository에 다시 접근해서 count,sum 하는 방법 중에서 어떤 것이 메모리나 시간적으로 효율적일지 알아볼 필요가 있다..!

profile
개발자가 되기 위해서 공부중입니다 :ㅡ)

2개의 댓글

comment-user-thumbnail
2024년 2월 26일

조건 합은 방법이 아예 없는건가요?

1개의 답글