인프런 커뮤니티 질문&답변

kim님의 프로필 이미지
kim

작성한 질문수

자바 ORM 표준 JPA 프로그래밍 - 기본편

JPQL 함수

Group By질문드립니다.

해결된 질문

작성

·

3.6K

4

활용편에서 나올 내용같은데 공부하다 궁금해서 문의드립니다. ㅠㅠ

querydsl을 적용중이고

A Table

year  name   value

2019   a           10

2019   b           11

2019   c            11

2020   d             9

2020   e            20

이 있을 때 ,  year로 그룹핑해서 value가 맥스인값을 보여주고 싶습니다.

2019  (b,11) , (c,11)

2020  (e, 20)

이렇게 값을 가져오게 하고 싶습니다.

최종적으로는 (b,11) (c,11)(e,20)로 출력하고 싶습니다.

List<a>로 받고 싶습니다. 년도순, 이름순으로 값을 저장하고 싶습니다.

a는 Qa를 a로 받은 것입니다.

JPAQueryFactory.from(a).transform(groupBy(a.year).as(list(a)));

저는 이정도에서 더이상 진도가 안나네요. ㅠㅠ 

답변 9

3

김영한님의 프로필 이미지
김영한
지식공유자

네 kim님 or 연산이 들어가기 때문에 데이터베이스와 데이터 상황에 따라서 더 느려질 수 있습니다. 이 부분은 DB마다 최적화 여부가 다르기 때문에 본인의 데이터베이스에 맞는 선택이 필요합니다. 기본적으로 데이터베이스 쿼리 플랜을 보고, 선택하면 될 것 같습니다.

쿼리는 2번으로 분리하면 복잡한 문제가 쉽게 풀리는 경우가 많다는 것은 일반적인 상황을 말씀드린 거에요.

이번 케이스는 성능이 중요하다면 처음 해결하셨던 것 처럼 조인으로 푸는 것이, 저도 좋은 방법이라 생각합니다. 그런면에서 네이티브 쿼리를 쓰는게 더 나은 선택이라 생각합니다.

참고로 하이버네이트를 개발한 게빈킹도 JPQL(HQL)로 모든 문제를 해결하기 위해서 만든 것이 아니라고 했습니다. 저도 실무에서 5% 정도는 네이티브 쿼리를 사용하고 있습니다.

감사합니다.

P.s: 더 나은 방법이 없는지 끝까지 파시는 모습이 보기 좋네요^^ 결국 이런 고민들이 쌓여서 더 나은 개발자가 된다고 생각해요. 좋은 하루되세요. :)

2

kim님의 프로필 이미지
kim
질문자

정말 감사드립니다 ^^ 얻는 것이 많았습니다 

2

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요 kim님

QueryDSL은 결국 JPQL로 실행되기 때문에, JPQL의 제약에 묶이게 됩니다.

현재 하이버네이트 구현체는 FROM이나 JOIN 절에서 서브쿼리를 만들 수 없습니다.(SELECT, WHERE 절은 가능합니다.)

그래서 아쉽게도 작성해주신 SQL은 JOIN 절에 서브쿼리가 들어가기 때문에 JPQL이나 QueryDSL로 작성하는 것이 불가능합니다.

해결방안은 다음과 같습니다.

1. 쿼리를 2번 나누어 실행한다. (서브쿼리 부분을 별도의 JPQL이나 QueryDSL로 실행하고 결과를 받아서, 그 결과를 파라미터로 넘기는 JPQL이나 QueryDSL을 다시 실행합니다. 예를 들어서 처음 쿼리로 [2019, 11], [2020,20]이라는 결과를 받고, 그 다음에 이 값을 파라미터로 넘기는 쿼리를 실행해서 최종 결과를 받습니다.)

2. 네이티브 쿼리를 사용한다.

3. 다음과 같이 다른 방법으로 푼다. (WHERE 절에서는 서브쿼리가 가능하므로)

select name, value from test_entity

where concat(year, value) in (

  select concat(year, max(value))

  from test_entity

  group by year

);

그런데 이 경우 where in 절에 파라미터를 2개 동시에 넘길 수 없어서, 어쩔 수 없이 문자를 더했습니다. 결국 인덱스를 사용할 수 없기 때문에 데이터가 많은 경우 성능에 치명적입니다. 그래서 네이티브 쿼리를 직접 사용하거나, QueryDSL이나 JPQL을 사용하면 쿼리를 2번 나누어 실행하는 방법중 하나를 선택하시는 것을 권장합니다.

참고로 MULTIPLE COLUMN SUBQUERY를 지원하는 데이터베이스의 경우 where (year, value) in 처럼 in 절 파라미터를 concat 하지 않고도 여러게 동시에 매칭할 수 있는데, 데이터베이스에 따라 지원 여부가 다르므로, 이 방법으로 풀려면 네이티브 쿼리로 푸는 것이 맞습니다.

혹시 더 좋은 방법을 발견하시면 저에게도 알려주세요^^

감사합니다.

2

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. kim님

어떤 것을 원하시는지 대략적인 감은 오는데, 제가 더 정확한 답을 드리기 위해서, 추가로 내용이 더 필요합니다.

1. 테이블 DDL을 PK 포함해서, 정확하게 적어주세요.

2. 원하는 결과를 얻을 수 있는 DB SQL을 정확하게 작성해주세요.

그러면 제가 그 내용을 JPQL이나 QueryDSL로 가능한지 확인하고, 도움을 드릴 수 있을 것 같습니다.

참고로 JPQL이나 QueryDSL도 결국 SQL이 실행되는 것이어서 SQL로 가능한 부분만 할 수 있습니다.

그럼 추가내용 기다릴께요.

1

어제부터 쿼리 dsl 공부 시작하면서 저도 한번 문제 풀어봤습니다!

List<Tuple> fetch = qf.select(date.name, date.value)
.from(date)
.where(date.value.eq(JPAExpressions.select(
q2.value.max()).from(q2).where(date.year.eq(q2.year))))
.fetch();

결과

결과는 맞게 나오는데 잘 푼건지는 잘 모르겠네요..

1

kim님의 프로필 이미지
kim
질문자

답변 정말 감사합니다 ^^~ . 많은 것을 얻어가는 것 같습니다. 

1. BooleanBuilder를 사용해서 동적으로 해서 쿼리 한번에 가는 것은 보장했는데 오히려 속도는 느려졌네요.

결과는 8개입니다.  8번 쿼리 날린 것(198ms)이랑 위의 답변처럼 

where (year=2019 and value=11) or (year=2020 and value = 20)

으로 1번 쿼리 보낸것(223ms)인데 더 느릴수도 있는 것인가요? 아님 인덱싱이 안되어서인가요?

2. 쿼리를 2번으로 나누면 문제를 풀 수 있는 다양한 해결 방안이 나오게 되는데,

=> 또 어떤 해결방식이 있는지 궁금합니다. 다른 sql방식으로도 구할 수 있을까요? 위의 sql는 제가 급조로 만들어본 것이라서요. max나 min값이 유일할 수없고 중복될 수도 있다는 생각에 list로 받아오게 한것도 있습니다.

1

김영한님의 프로필 이미지
김영한
지식공유자

네 우선 쿼리가 100번 호출되면 성능이 너무 안나오니, 쿼리를 2번 나누어 시도하려면 정말 2번 안에 해결될 때 시도하시면 될 것 같아요. 쿼리를 2번으로 나누면 문제를 풀 수 있는 다양한 해결 방안이 나오게 되는데, 이번 예라면 in 대신에 다음과 같이 풀면 될 것 같습니다^^ (물론 이 경우에도 DB에 따라 index를 잘 탈 수 있는지 확인이 필요합니다.)

SELECT * FROM TEST_ENTITY

where (year=2019 and value=11) or (year=2020 and value = 20)

그리고 통계성 쿼리 같이 복잡한 경우는 JPQL(QueryDSL)을 고집하기 보다는 네이티브 쿼리로 푸는 것이 더 적절한 선택이 될 때가 있습니다.

감사합니다.

1

kim님의 프로필 이미지
kim
질문자

답변 감사드립니다

왠지 쿼리 한번에 다 해결해야되는 그런 생각을 가지고 있었던것 같습니다. 네이티브쿼리도 생각해보았는데 jpa쓰는데 네이티브쓰면 안좋은거라고도 생각했던가 같아요 호

2번에 나누어서 한번 시도해보겠습니다

그런데 2번에 나누어 실행하면 예를 들어 첫번째 결과가 100개이면 db호출이 100번 일어날 수밖에 없는상황이 되는거죠? 컬럼이 2개라서 in을 사용하지 못할거 같아서요

도윰이 많이 돠었습니다

1

kim님의 프로필 이미지
kim
질문자

CREATE TABLE TEST(ID INT PRIMARY KEY,

   YEAR VARCHAR(4),

   NAME VARCHAR(255),

   VALUE DOUBLE);

COMMIT;

INSERT INTO TEST VALUES(1, '2019','A', 10);

INSERT INTO TEST VALUES(2, '2019','B', 11);

INSERT INTO TEST VALUES(3, '2019','C', 11);

INSERT INTO TEST VALUES(4, '2020','D', 9);

INSERT INTO TEST VALUES(5, '2020','E', 20);

COMMIT;

SELECT * FROM TEST;

SELECT TEST1.NAME, TEST1.VALUE

FROM TEST TEST1

INNER JOIN

(SELECT YEAR, MAX(VALUE) VALUE

FROM TEST

GROUP BY YEAR) TEST2

ON TEST1.YEAR = TEST2.YEAR

AND TEST1.VALUE=TEST2.VALUE;

쿼리가 이게 맞는지 모르겠는데. 일단 결과값은 나오고 H2에서 방금 작성했습니다.

이 결과값을 

CLASS TestDto{

 private name;

 private value;

 private year;

}

에 @QueryProjection을 사용해서 매핑하려 합니다.

도움 정말 감사드립니다~~

kim님의 프로필 이미지
kim

작성한 질문수

질문하기