안녕하세요 개인프로젝트로 게시글의 댓글을 조회하는 sql을 짜고있습니다.
현재 Comment 테이블의 컬럼은 comment_id, content, writer_id, post_id, parent_id 이외의 컬럼이 있고, parent_id는 대댓글일 경우 자신의 부모 댓글 id 가 있고, 부모댓글인 경우는 null입니다. 외래키는 writer_id, post_id, parent_id 입니다.
select c.comment_id, c.content, c.comment_status, c.user_id, c.parent_id, c.created_at, w.nickname, c2.comment_id, c2.content, c2.comment_status, c2.created_at, c2.user_id, w2.nickname from comment c join user w on c.user_id = w.user_id left join comment c2 on c.comment_id = c2.parent_id left join user w2 on c2.user_id = w2.user_id where c.post_id = 1 and c.parent_id is null order by c.comment_id,c2.comment_id;
해당 쿼리는 self join으로 부모와 연관된 자식 댓글을 조회하고 writer와 조인 후 조건에 맞는 레코드만 남기고 정렬을 하는 쿼리입니다. 어플리케이션에서 사용하는 라이브러리로 계층형태의 DTO convert가 편하지만 실행 계획을 보면 임시테이블을 생성하고 ,filesort도 일어나게되어 성능하락이 고민되어 생각한 다른 방법은
select c.comment_id, c.content, c.comment_status, c.user_id, c.parent_id, c.created_at, w.nickname from comment c join user w on c.user_id = w.user_id where c.post_id = 1 order by c.comment_id;
해당 쿼리로 조회해 어플리케이션에서 loop를 돌면서 변환하는 방식입니다. 두가지 방법 중 어떤 것을 선택해야되는지 모르겠어서 질문드립니다. 첫번째 방법같은 경우 쿼리를 개선할 방법이 있다면 조어 부탁드립니다.
답변 감사드립니다.
1. 대댓글이 존재하는 구조이고 대댓글의 계층을 1로 제한했기 때문에 parent_id 가 null 이냐 아니냐 여부로 부모 댓글과 대댓글을 구분합니다. 그래서 1번 쿼리로 조회를 하게되면 다음과 같은 결과 나옵니다.
Comment comment_id | content| parent_id 1 "sdf" null 2 "sdf" 1 3 "sdf" 1 4 "sdf" null 5 "sdf" 4 6 "sdf" null
부모댓글과 자식 댓글 셀프조인 후 comment_id | content| parent_id | child_id | child_content 1 "sdf" null 2 "sdf" 1 "sdf" null 3 "sdf" 4 "sdf" null 5 "sdf" 6 "sdf" null null null
이렇게 조회 후 spring 어플리케이션에서 사용하는 라이브러리로 아래와 같이 조회하면서 변환합니다.
class Comment { Long parentId; List<ChildComment> childComments; }
두가지 쿼리 둘다 어플리케이션에서 변환하기 때문에 차라리 2번 쿼리 방법으로 post_id에 해당하는 comment 만 필터링해서 가져오고 처리하는 방식이 더 낫지 않을까라는 고민이 들어서 질문드렸습니다. 경력도 없는 취준생이어서 구조가 이해가 안되실 수도 있겠네요 ㅜㅜ
2. 페이징 처리는 서비스 성격상 많은 댓글이 달릴거라 생각하지 않아서 아직 고려하지 않고 구현중입니다.!!
아 제가 질문을 명확하게 하지 못했나보네요.
프론트의 요구사항으로 게시글 본문을 조회하는 api와 본문의 댓글을 조회하는 api가 따로 있습니다.
말씀해주신대로 본글의 ID가 post_id 맞습니다. 1번 쿼리는 order by 에서 c.comment_id 만 존재하면 실행 계획에서 임시테이블과 filesort가 발생하지 않는데 c2.coment_id를 추가하면 발생하게 됩니다.
어플에서 조회결과를 1번 쿼리로 조회하면 원하는 형태로 자동 변환하지만 2번 쿼리는 결과를 받아서 제가 변환시켜줘야됩니다. 두가지 방법 모두 어플리케이션에서 변환하는 것은 똑같고 자동으로 하냐 제가 직접하냐의 차이입니다.
Comment comment_id | content| parent_id | post_id | writer_id 1 "sdf" null 1 1 2 "sdf" 1 1 2 3 "sdf" 1 1 1 4 "sdf" null 1 3 5 "sdf" 4 1 1 6 "sdf" null 1 4 7 "sdf" null 2 3
댓글 대댓글 모두 위와 같이 post_id 가 존재합니다.
comment_id pk 인덱스, writer_id, parent_id, post_id 가 외래키여서 각각 인덱스가 있습니다.
{ "parentId" : 1, "content" : "댓글", "writerNickname" : "nickname", "childComments" : [ { "childId": 2, "content": "대댓글", "writerNickname" : "nickname", }, { "childId": 3, "content": "대댓글", "writerNickname" : "nickname", }], "parentId" : 4, "content" : "댓글", "writerNickname" : "nickname", "childComments" : [ { "childId": 5, "content": "대댓글", "writerNickname" : "nickname", }, ], "parentId" : 6, "content" : "댓글", "writerNickname" : "nickname", "childComments" : [ ] }
위와 같은 형태로 json 응답을 프론트에게 주고 싶습니다 !!
-- json 형식까지는 아니어도 일단 정렬만 추가해 봤습니다. SELECT c.comment_id , c.content , c.comment_status , c.user_id , c.parent_id , c.created_at , w.nickname FROM comment c JOIN user w ON c.user_id = w.user_id WHERE c.post_id = 1 ORDER BY IFNULL(c.parent_id, c.comment_id), c.comment_id -- MySQL ; ORDER BY IFNULL(c.parent_id, c.comment_id), c.comment_id -- MySQL ORDER BY ISNULL(c.parent_id, c.comment_id), c.comment_id -- MSSQL ORDER BY NVL(c.parent_id, c.comment_id), c.comment_id -- Oracle