더미 데이터 만들기
insert into reply_tb(comment, board_id, user_id, created_at) values('댓글1', 1, 1, now());
insert into reply_tb(comment, board_id, user_id, created_at) values('댓글2', 4, 1, now());
insert into reply_tb(comment, board_id, user_id, created_at) values('댓글3', 4, 1, now());
insert into reply_tb(comment, board_id, user_id, created_at) values('댓글4', 4, 2, now());
SQL
복사
h2 콘솔 확인 → reply_tb
h2 콘솔 확인 → board_tb
쿼리 연습
이너 조인 (게시글 목록보기 - 댓글도 함께 보이기)
select * from board_tb bt inner join reply_tb rt on bt.id=rt.board_id
SQL
복사
outer join연습1 - 필요한 칼럼 뽑아내기
select
bt.id, bt.title, bt.content, bt.user_id, bt.created_at,
ifnull(rt.id, 0) rid, rt.board_id, ifnull(rt.comment,'')
from
board_tb bt
left outer join
reply_tb rt on bt.id = rt.board_id;
// board테이블을 기준으로 reply테이블을 board_tb의 id 와 reply_tb의 board_id가
// 같은 값을 가진 행을 outer join(값이 없으면 null로 표시되는)한다.
SQL
복사
Additional Notes
ifnull( a , b) 쿼리 문법
값이 null이 아닐 경우에는 a
값이 null일 경우에는 b
내가 선택한 테이블을 보이게 하고 싶을 때. 아우터 조인 사용
조인의 목적은 두개의 테이블을 다 보여주고 싶을 때 사용
outer join연습2 - 모든 열 표시
쿼리문 한글 해석
board테이블의 id와reply_tb의 board_id가 같은 행을
board 테이블을 기준으로left outer join 하여
bt의 모든 칼럼과 rt의 모든 칼럼을 표시한다. (null도 표시 (outer join))
select bt.*, rt.* from board_tb bt left outer join reply_tb rt on bt.id = rt.board_id;
SQL
복사
outer join연습3
쿼리문 한글 해석
board 테이블과 reply테이블의 관계에서
bt테이블의 id와 rt테이블의 board_id의 값이 같은 행을
board테이블의 기준으로 (left) outer join 을 하여 테이블을 생성하고
거기서 board테이블의 id 값이 2인 것만 출력한다.
select * from board_tb bt left outer join reply_tb rt on bt.id=rt.board_id where bt.id=2
SQL
복사
outer join (게시글 목록보기 - 댓글 카운트 보이기)
쿼리문 한글 해석
board테이블과 reply테이블의 관계에서
board테이블의 id와 reply테이블의 board_id가 같은 행을
board테이블을 기준으로 (left)outer join 한다. —> 여기까지는 결과값 6행
group by 함수로 중복되는 값들을 한 행으로 표현한다. [ board테이블의 id, title, content, user_id, created_at ]
여기까지는 다른 board 칼럼들은 한 행으로 표현이 되었는데, 댓글은 여러 개가 있기 때문에 한 행으로 표현할 수 없다. 그래서 한 행으로 표현해 줄수 있는 sum,avg,count같은 연산자가 필요하고 여기서는 count가 사용되었다.
select
bt.*, count(rt.id)
from
board_tb bt
left outer join
reply_tb rt on bt.id = rt.board_id
group by
bt.id, bt.title, bt.content, bt.user_id, bt.created_at;
SQL
복사
Group By더 알아보기..
GPT에게 최대한 이해하기 쉽게 답변을 유도하여 정리를 해보았다.
그럼
게시물 1 , 댓글 id = 1
게시물 1 , 댓글 id = 2
게시물 2 , 댓글 없음
게시물 3 , 댓글 id = 1
게시물 3 , 댓글 id = 2
게시물 3 , 댓글 id = 3
SQL
복사
이런 테이블이 있다면
group by 게시글 명령어로 각 게시물의 같은 번호라면 하나로 묶어지고, 나머지 한 게시물에 달린 여러 댓글에는 한 행(row)으로 표현하기 위한 연산이 필요해지는거야?
답변 펼쳐보기
select
bt.id, bt.title, bt.content, bt.user_id, but.username, bt.created_at,
rt.id r_id, rt.user_id r_user_id, rut.username, rt.comment
from
board_tb bt
left outer join
reply_tb rt on bt.id = rt.board_id
inner join
user_tb but on bt.user_id = but.id
left outer join
user_tb rut on rt.user_id = rut.id
where
bt.id = 4;
SQL
복사
쿼리문 한글해석
board테이블과 reply테이블의 관계에서
board테이블의 id와 reply테이블의 board_id가 같은 행을
board테이블을 기준으로 (left) outer join을 한다.
여기서 얻어진 테이블과 user테이블의 관계에서
board테이블 user_id와 user테이블 id 값이 같은 행으로 두 테이블을 붙인다 (inner join)
여기서 얻어진 테이블과 user테이블의 관계에서
reply테이블의 user_id와 user테이블의 id가 같은 행을
직전에 얻어진 테이블(inner join된)을 기준으로 (left) outer join한다.
최종적으로 얻어진 테이블에서board테이블 id가 4인 값을 조회한후 board테이블의 [ id, title, content, user_id, created_at ] 와reply테이블의 [ id, user_id, username, commnet ] 그리고 user 테이블의 [ username ]칼럼으로 조회한다.
화면 연동하기
화면 먼저 확인
현재 필요한 로직!
화면에 출력할 때, for문을 6번 돌 게 될 것이다.
그러면, 게시글이 6번 출력되지만, 게시글은 4번만 출력되어야 한다.
즉, 위 데이터 행을 DTO에 담고, 전체를 컬렉션에 담으면 화면에 뿌릴때 문제가 된다.
그래서 DB에서 조회된 데이터를 담는 DTO가 있고, 해당 데이터 화면을 위한 DTO에 옮겨 담는 과정이 필요하다.
직접 파싱해서 사용하기
직접 파싱해서 사용하는 원초적 방법!
일단 조회된 내용을 rows에 배열로 다 담는다. 인덱스마다 배열이 담긴 List이다.
따로 담을 그릇들 준비 (boardList, replyList)
저장된 각각의 배열을 인덱스로 모두 따로 꺼내서 옮겨담는다. for each 문
BoardDTO객체를 생성하면서 파싱된 필드값을 옮겨 담는다.
생성된 board객체를 boardList에 추가한다.
조회된 row배열에서 댓글 정보가 저장된 값을 파싱한다.
파싱된 댓글 정보를 replyDTO로 옮겨 담는다.
생성된 reply객체를 replyList에 추가한다.
이렇게 하면 boardList와 replyList는 size가 6인 배열이 생성되었다. 하지만 boardList는 3~5번지가 중복된 값이 들어가있다. (총 0~5번지)

스트림을 이용하여 중복된 값을 합친다. distinct사용 -> 문법 기억하자.

boardList를 순회한다. boardDTO에 댓글객체를 ORM매핑한다. 게시글:댓글=1:N 관계

replyList를 순회한다.

순회시에 게시글 번호와 댓글이 참조하는 게시글 번호가 같다면 reply 추가한다.
public List<BoardResponse.BoardDTO> findAllV2() {
Query query = em.createNativeQuery("select bt.id, bt.title, bt.content, bt.user_id, bt.created_at, ifnull(rt.id, 0) rid, rt.board_id, ifnull(rt.comment,'') from board_tb bt left outer join reply_tb rt on bt.id = rt.board_id");
// 1. 일단 조회된 내용을 rows에 배열로 다 담는다. 인덱스마다 배열이 담긴 List이다.
List<Object[]> rows = (List<Object[]>) query.getResultList();
// 2. 따로 담을 그릇들
List<BoardResponse.BoardDTO> boardList = new ArrayList<>();
List<BoardResponse.ReplyDTO> replyList = new ArrayList<>();
for (Object[] row : rows) {
// 3. 저장된 각각의 배열을 인덱스로 모두 따로 꺼내서 옮겨담는다.
Integer id = (Integer) row[0];
String title = (String) row[1];
String content = (String) row[2];
Integer userId = (Integer) row[3];
Timestamp createdAt = (Timestamp) row[4];
// 4. BoardDTO를 생성하면서 꺼내놓은 필드값을 옮겨 담는다.
BoardResponse.BoardDTO board = new BoardResponse.BoardDTO(
id, title, content, userId, createdAt.toLocalDateTime()
);
// 5. 생성된 board객체를 boardList에 추가한다.
boardList.add(board);
// 6. 조회된 row배열에서 댓글 정보가 저장된 값을 파싱한다.
Integer rid = (Integer) row[5];
Integer boardId = (Integer) row[6];
String comment = (String) row[7];
// 7. 파싱된 댓글 정보를 replyDTO로 옮겨 담는다.
BoardResponse.ReplyDTO reply = new BoardResponse.ReplyDTO(
rid, boardId, comment
);
// 8. 생성된 reply객체를 replyList에 추가한다.
replyList.add(reply);
}// 9. 이렇게 하면 boardList와 replyList는 size가 6인 배열이 생성되었다.
// 하지만 boardList는 3~5번지가 중복된 값이 들어가있다. (총 0~5번지)
// 10. 스트림을 이용하여 중복된 값을 합친다. distinct사용 -> 문법 기억하자.
boardList = boardList.stream().distinct().toList();
// 11. boardList를 순회한다. boardDTO에 댓글객체를 ORM매핑한다. 게시글:댓글=1:N 관계
for (BoardResponse.BoardDTO b : boardList){
// 12. replyList를 순회한다.
for (BoardResponse.ReplyDTO r : replyList){
// 13. 순회시에 게시글 번호와 댓글이 참조하는 게시글 번호가 같다면 reply 추가한다.
if(b.getId() == r.getBoardId()){
b.addReply(r);
}
}
}
return boardList;
}
SQL
복사