- 1) 로그인 중인 회원번호를 조건으로, 2) 문서 추천 로그 테이블과 댓글 추천 로그 테이블에서, 3) document_srl 및 comment_srl의 목록을 가져오고, 4) list_count와 page 정보를 통해 목록 중에서 출력해야 할 항목들만 슬라이스하여, 5) 문서 및 댓글 객체 정보를 가져오는 방식입니다.
- 그런데 직접 db 쿼리를 날리는 방식(아래의 A. 직접 쿼리 날리기)과 두 개의 xml 쿼리를 통해 얻은 문서 목록과 댓글 목록을 병합하는 방식(아래아래의 B. xml로 목록 두 개를 가져와서 병합하기) 중 어느 것이 더 효율적일지 고민이 좀 됩니다.
- 둘 중 어느 것이 좋을지, 아니면 제3의 방식이 있을지, 조언 좀 부탁드립니다!!
A. 직접 쿼리 날리기
$args = new stdClass; $args->member_srl = $this->user->member_srl; $args->list_count = 20; $args->page_count = 5; $args->page = Context::get('page') ? intval(Context::get('page')): 1; (중략) $oDB = DB::getInstance(); $query = ' (SELECT document_voted_log.document_srl, null as comment_srl, document_voted_log.regdate as voted_date FROM document_voted_log LEFT JOIN comment_voted_log ON document_voted_log.member_srl = comment_voted_log.member_srl WHERE document_voted_log.member_srl = ?) UNION (SELECT null as document_srl, comment_voted_log.comment_srl, comment_voted_log.regdate as voted_date FROM document_voted_log RIGHT JOIN comment_voted_log ON document_voted_log.member_srl = comment_voted_log.member_srl WHERE comment_voted_log.member_srl = ?) ORDER BY voted_date DESC '; $stmt = $oDB->query($query, [$args->member_srl, $args->member_srl]); $result = $stmt->fetchAll(); $count = count($result); $limit = $args->list_count; if ( $args->page > 1 ) { $limit = $args->list_count * ($args->page - 1); } $output = new stdClass; $output->data = array(); foreach ( $result as $key => $attribute ) { if ( $args->page === 1 && $key >= $limit ) { continue; } if ( $args->page > 1 && ($key < $limit || $key >= $limit + $args->list_count) ) { continue; } if ( isset($attribute->document_srl) ) { $oDocument = DocumentModel::getDocument($attribute->document_srl); $oDocument->variables['voted_date'] = $attribute->voted_date; $output->data[$count - $key] = $oDocument; } else if ( isset($attribute->comment_srl) ) { $oComment = CommentModel::getComment($attribute->comment_srl); $oComment->variables['voted_date'] = $attribute->voted_date; $output->data[$count - $key] = $oComment; } } $output->total_count = $count; $output->total_page = ceil($output->total_count / $args->list_count); $output->page = $args->page; $output->page_navigation = new PageHandler($output->total_count, $output->total_page, $output->page, $args->page_count);
B. xml로 목록 두 개를 가져와서 병합하기
$args = new stdClass; $args->member_srl = $this->user->member_srl; $args->list_count = $list_count = 20; $args->page_count = 5; $args->page = $page = Context::get('page') ? intval(Context::get('page')): 1; (중략) $document_output = executeQueryArray('voted.getTotalVotedDocuments', $args); $comment_output = executeQueryArray('voted.getTotalVotedComments', $args); // 글 목록과 댓글 목록을 병합하고 추천일(voted_date) 기준으로 재정렬 $result = array_merge($document_output->data, $comment_output->data); if ( empty($result) ) { return; } usort($result, fn($a, $b) => strcmp($b->voted_date, $a->voted_date)); // 글과 댓글의 카운트와 페이지 재계산 $output = new stdClass; $output->total_count = $total_count = count($result); $output->total_page = $total_page = ceil($total_count / $list_count); $output->page = $page; // 목록의 항목들에 들어갈 객체 변수를 가져옴 $limit = $list_count; if ( $page > 1 ) { $limit = $list_count * ($page - 1); } $output->data = array(); foreach ( $result as $key => $attribute ) { // 현재 페이지에 들어가지 않는 항목은 건너뜀 if ( $page === 1 && $key >= $limit ) { continue; } if ( $page > 1 && ($key < $limit || $key >= $limit + $list_count) ) { continue; } // 문서 및 댓글 객체를 가져옴 if ( isset($attribute->document_srl) ) { $oDocument = DocumentModel::getDocument($attribute->document_srl); $oDocument->variables['voted_date'] = $attribute->voted_date; $output->data[$total_count - $key] = $oDocument; } else if ( isset($attribute->comment_srl) ) { $oComment = CommentModel::getComment($attribute->comment_srl); $oComment->variables['voted_date'] = $attribute->voted_date; $output->data[$total_count - $key] = $oComment; } } // 페이지네비게이션 정의 $output->page_navigation = new PageHandler($output->total_count, $output->total_page, $output->page, $args->page_count);
- 첫 번째 방식에서 LEFT JOIN, RIGHT JOIN은 왜 필요한 거죠?
- UNION 후에 ORDER BY를 하면 사실상 모조리 불러와서 다시 정렬하는 셈입니다.
- 하나씩 getDocument getComment 하는 것도 매우 비효율적이예요. 최소한 getDocuments로 여러 개를 한 번에 불러오셔야...
- 두 번째 방식은 각각의 쿼리가 어떻게 생겼는지 모르겠지만 이후에 버리는 데이터가 많네요. 2페이지 이후로 넘어갔을 때는 페이지 계산이 무척 난감해질 것 같고요.
- fn($a, $b) => 이거 상당히 최근 버전이 아니면 문법 오류날 거예요.
- 병합하지 마세요.
- 병합하지 마시라고요. ㅠㅠ
- 정 하고 싶으면 글 50만개, 댓글 500만개 정도 추천기록 더미데이터를 만들어 넣어놓고 실제로 테스트해 보세요.
- 정석대로 구현한다면 추천이 발생할 때마다 트리거를 사용해서 별도의 테이블에 모든 추천기록을 쌓아야 합니다. 이걸 단순히 시간역순으로 정렬해서 가져오고, documents, comments 테이블을 조인해서 해당 문서와 댓글까지 한 번에 가져오는 거죠. 별도의 테이블을 사용한다면 댓글을 글마다 묶어서 보여주는 등, 훨씬 복잡한 기능도 무리 없이 구현 가능합니다.