3차 프로젝트에서 "삭제된 댓글입니다."를 출력하려다가 머리를 다 쥐어뜯는 중이다..
0. 나를 알고 적을 알아야 이긴다.
# 구현하고 싶은 화면
블라인드는 삭제된 댓글의 답글은 남기고 이렇게 보여주더라
다음/네이버 카페도 댓글에 답글이 있으면 삭제된 댓글이라고 출력했다.
추가로 유튜브는 재댓글이 달려있어도, 댓글을 지우면 다 삭제되더라.
아마 빅데이터에 다 저장하고 지우는 거겠지? 😏 무작정 지우진 않을 것 같다. 분명
# 현 댓글 구조
댓글은 2가지 레벨(Class)로 구분된다.
1. 게시글에 단 댓글 - 출력 화면에서 "삭제 예정" 최상위 댓글
2. 댓글에 단 댓글 - 출력 화면에서 "삭제 예정" 하위 댓글
2번 하위 댓글은 두가지로 구분할 수 있다.
댓글에 단 댓글에 또 댓글을 달려고 했을 때, 상위 댓글의 작성자 정보를 가져와 저장한다.
정리해서 말하자면 출력 화면에 "댓글 멘션 댓글"은 "삭제 예정"에 답글을 추가한 것이 아니라 하단 "댓글에 댓글"에 작성 중 하나에 작성을 한 것이다.
# DB
DB테이블은 아래와 같다.
댓글 계층형 참고 블로그는 여기 https://xerar.tistory.com/44
이 상태에서 Class가 0인 게시글에 달린 최초 댓글 "삭제 예정"을 삭제하면 해당 자리에 "삭제된 댓글입니다." 이 출력되어야 하는 것이다.
"삭제된 댓글입니다."는 아래 경우가 충족되면 출력되어야 한다.
1. 댓글에 답글이 1개 이상 달려있는 경우
2. 동일한 최상위 댓글을 가지는 댓글들 중 최상위 댓글이 존재하지 않는 경우
# 생각해본 방법
1. 개발 단에서 체크해서 class=0인 값이 출력되지 않고 작업해주어도 되는데, 그러면 for을 돌려서 모든 댓글에 어떠한 검사를 해야 하니...... 쿼리에서 해결해보고 싶었다.
2. 보다 쉬운 방법으로는 댓글 상태를 변경하거나, 삭제된 댓글 테이블을 만들어서 실제 "삭제"를 하지 않고 관리하는 방법도 있지만 나는 이런 방법은 사용하고 싶지 않았다.
내가 제작하던 사이트는 일반 커뮤니티였는데, 댓글은 크게 중요한 데이터가 아닌데 DB에 남겨두는 건 좋지 않다고 생각했기 때문이다. (DB 낭비는 죄악이야. 빅데이터 제외하구-쉿😏)
✔ 쿼리에서 최대한 작업해서 백단에서의 작업을 줄여보자! |
1. 답글이 달려있지만 최상위 댓글이 삭제된 경우의 최상위 댓글 PK를 가져오자
SELECT co_p_no from comment
WHERE post_no = 114
GROUP BY co_p_no
having avg(co_class) = 1
댓글 테이블에서 최상위 댓글PK값끼리 그룹 지어, avg(co_class)가 1인 결과 값을 선택했다.
avg(co_class)가 1인 것을 결과 값으로 한 이유는 co_class가 0인 값이 없으면 (최상위 댓글이 삭제된 상황! 우리가 찾는 것) 최상위 댓글 별 평균이 1이 나오기 때문이다.
결과를 보아하니 지금 114게시글에 달린 댓글 중에 우리가 찾는 상황은 최상위 댓글이 317/478/479인 상황인 거 같다.
이제 이거로 어쩌지;;
댓글 목록을 출력할때 구분되게 할 거니까.. 댓글 목록과 합쳐보자!!!!
2. 우리가 특정한 경우를 댓글 목록에서 구분되게 하기
SELECT if(msg = "최상위댓글x", msg, "") AS result, b.* from
(SELECT co_p_no, "최상위댓글x" AS msg from comment
WHERE post_no = 114
GROUP BY co_p_no
having avg(co_class) = 1
) a
right JOIN comment b ON b.co_p_no = a.co_p_no
ORDER BY co_p_no desc, co_class, co_order
기존 댓글 테이블과 right join해주었다.
그렇다면 이제 할 일은
result 값이 최상위 댓글 X인 것 중에 co_p_no가 가장 작은 값 위에 "댓글이 삭제되었습니다"라는 임시 row를 올려주어야 하는데, 이건 정말 아무리 생각해도 모르겠더라. (선생님도 모르겠다 하셨다..😐 나 울어)
그래서 어쩔 수 없이 개발단에서 처리가 필요하겠다 생각을 했다.
그럼 여기서 내가 쿼리를 더 도움이 되게 만드는 방법은 뭐가 있을까 고민했다.
그리하여 삭제된 댓글입니다가 출력되어야 하는 바로 다음 댓글 pk값을 받아오자고 방향을 바꿨다.
3. 삭제된 댓글입니다가 출력되어야 하는 바로 다음 댓글 pk값을 받아오자
SELECT a.co_p_no, a.co_no, a.co_order from
(SELECT if(msg = "최상위댓글x", 1, 0) AS result, b.* from
(SELECT co_p_no, "최상위댓글x" AS msg from comment
WHERE post_no = 114
GROUP BY co_p_no
having avg(co_class) = 1
) a
right JOIN comment b ON b.co_p_no = a.co_p_no
ORDER BY co_p_no desc, co_class, co_order) a
WHERE a.result = 1
여기서 최상위 댓글 그룹(co_p_no)에서 순서(or_order)가 첫 번째 댓글(co_order가 가장 작은 것)의 댓글 pk(co_no)를 가져오고 싶다.
초록색으로 체크한 row만 가져오려면 이제 어떻게 할까
그룹으로 묶어주자
4. GROUP BY 관련한 MYSQL 5.7 업데이트...
SELECT a.co_no, min(a.co_order) from
(SELECT if(msg = "최상위댓글x", 1, 0) AS result, b.* from
(SELECT co_p_no, "최상위댓글x" AS msg from comment
WHERE post_no = 114
GROUP BY co_p_no
having avg(co_class) = 1
) a
right JOIN comment b ON b.co_p_no = a.co_p_no
ORDER BY co_p_no desc, co_class, co_order) a
WHERE a.result = 1
GROUP BY a.co_p_no
최상위 댓글로 묶어주고 min값을 뽑아오려는데
(1055): Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'a.co_no' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_byuery |
난생처음 보는 오류가 났다.
찾아보니까 MYSQL 5.7로 업데이트되면서 SELECT 칼럼을 하려면 GROUP BY절에 컬럼을 적어주라고 한다..
?? 네?
https://info-lab.tistory.com/274
MYSQL설정을 바꾸는 방법도 있다는데.... MYSQL이 바꾼걸 내가 다시 돌리는 것도 조금 그래서
MYSQL에서 하란대로 쿼리를 바꿔보았다
5. 하란대로 했어요!
SELECT a.co_p_no, a.co_no, min(a.co_order) from
(SELECT if(msg = "최상위댓글x", 1, 0) AS result, b.* from
(SELECT co_p_no, "최상위댓글x" AS msg from comment
WHERE post_no = 114
GROUP BY co_p_no
having avg(co_class) = 1
) a
right JOIN comment b ON b.co_p_no = a.co_p_no
ORDER BY co_p_no desc, co_class, co_order) a
WHERE a.result = 1
GROUP BY a.co_p_no, a.co_no
GROUP BY절에 a.co_no를 추가했다.
GROUP BY 절에 두 가지 칼럼을 넣어서, 출력이 되긴 하는데, a.co_no를 기준으로 두 번째 그룹이 묶여서 원하는 값이 출력되지 않았다.
여기서 선생님께 처음으로 들고 갔는데
선생님께서도 고개를 저으셨다고....😭🥵 개발자는 백 단을 잘하면 되고 DB는 DBA가 할 거니까 이렇게 돌아갈 필요가 없다고 하셨다. 4시간 정도 고민했는데, 너무 허무했다.
그래서 그냥 포기하고 블로그 쓰면서 고민한 거 정리나 해야 했다..
그런데..!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
마지막으로 한 번만 더 보자 싶어서 "group by 작은 값 찾기 ROW"키워드 검색을 해서
분명 보았던 글을 신선해진 뇌로 다시 해봤다. (방금 일어남)
https://helloino.tistory.com/120
여기서 힌트를 얻어서 다시 도전
6. 최상위 댓글 그룹에서 가장 작은 값을 가져와서 댓글 pk 찾기
SELECT a.co_p_no, a.co_no FROM comment a
RIGHT JOIN
(SELECT a.co_p_no, min(a.co_order) AS minOrder from
(SELECT if(msg = "최상위댓글x", 1, 0) AS result, b.* from
(SELECT co_p_no, "최상위댓글x" AS msg from comment
WHERE post_no = 114
GROUP BY co_p_no
having avg(co_class) = 1
) a
right JOIN comment b ON b.co_p_no = a.co_p_no) a
WHERE a.result = 1
GROUP BY a.co_p_no) b
ON a.co_p_no = b.co_p_no AND a.co_order = b.minOrder
욕심부리지 말고 천천히 하는 거다
바로 전 단계에서 바로 co_no를 뽑을 수 없었으니. co_p_no별로 가장 작은 order을 가져오고
그 order과 co_p_no를 통해 commnet테이블에서 찾아버렸다. 값을.......😁 감격
7. 댓글 목록으로 출력해보자! 야호
SELECT IFNULL(b.result,0) AS isDelect, a.* FROM comment a
left JOIN
(SELECT 1 AS result, a.co_p_no, min(a.co_order) AS minOrder from
(SELECT if(result = 1, result, 0) AS result, b.* from
(SELECT co_p_no, 1 AS result from comment
WHERE post_no = 114
GROUP BY co_p_no
having avg(co_class) = 1
) a
right JOIN comment b ON b.co_p_no = a.co_p_no) a
WHERE a.result = 1
GROUP BY a.co_p_no) b
ON a.co_p_no = b.co_p_no AND a.co_order = b.minOrder
WHERE post_no = 114
ORDER BY co_p_no desc, co_class, co_order
이제 개발단에서 msg칼럼을 받아와서 뿌려줄 때 msg에 값이 있으면 그전에 댓글이 삭제되었다고 출력해주면 된다!ㅎㅎ
8. view단 작업
<li th:class="'commont-line dept_'+${comment.comClass}" th:each="comment : ${comments}"
th:id="'comment_'+${comment.comNo}"
th:if="${comments!=null and !comments.isEmpty()}">
<!-- 댓글 내용 -->
<div class="delected-comment" th:if="${comment.isDelect == 1}">
<div class="cmtContent">
작성자가 삭제한 댓글입니다.
</div>
</div>
<div class="comment-inner">
<div class="info">
<span class="memNick" th:text="${comment.memNick}"></span><span
th:text="${comment.writeday}"></span><input name="memNo"
th:value="${comment.memNo}"
type="hidden">
</div>
<div class="cmtContent">
<a class="comment-mention" th:href="@{'/member/'+${comment.memNo}}"
th:if="${comment.parentMemNick != null}"
th:text="'@'+${comment.parentMemNick}"></a><span
th:text="${comment.content}"></span>
</div>
<!-- 댓글 설정 메뉴 -->
<div class="comment-menu">
<i aria-hidden="true" class="fa fa-ellipsis-v comment-menu__btn"></i>
<ul style="display:none">
<li th:if="${session.member != null && comment.memNo == session.member.memNo}">
<a
class="link-dark modifyFormBtn" href="#">수정</a></li>
<li th:if="${session.member != null && comment.memNo == session.member.memNo}">
<a
class="link-dark removeBtn" href="#">삭제</a></li>
<li><a class="link-dark commentBlcokBtn" href="#">신고</a></li>
</ul>
</div>
<!-- 답글 -->
<i aria-hidden="true" class="fa fa-commenting"></i><a
class="btn link-dark dept2CommentOpenBtn"
href="#">답글</a>
<div aria-expanded="true" class="dept2Comment" style="display:none">
<div class="input-group">
<textarea class="form-control"
placeholder="명예회손, 무단광고, 불법정보 유포 시 삭제될 수 있습니다."></textarea>
<button class="btn btn-primary dept2CommentBtn">등록</button>
</div>
</div>
<input name="parents" th:value="${comment.parents}" type="hidden">
</div>
</li>
comment.isDelect == 1 인 경우 상단에 삭제된 댓글이라고 뜨게 코드를 추가했다.
css도 함께 수정했다.
9. 완성! 빅토리!