Extra Form
PHP PHP 7.4
CMS Rhymix 2.0

- 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);

 

  • profile

    - 첫 번째 방식에서 LEFT JOIN, RIGHT JOIN은 왜 필요한 거죠?

    - UNION 후에 ORDER BY를 하면 사실상 모조리 불러와서 다시 정렬하는 셈입니다.

    - 하나씩 getDocument getComment 하는 것도 매우 비효율적이예요. 최소한 getDocuments로 여러 개를 한 번에 불러오셔야...

    - 두 번째 방식은 각각의 쿼리가 어떻게 생겼는지 모르겠지만 이후에 버리는 데이터가 많네요. 2페이지 이후로 넘어갔을 때는 페이지 계산이 무척 난감해질 것 같고요.

    - fn($a, $b) => 이거 상당히 최근 버전이 아니면 문법 오류날 거예요.

    - 병합하지 마세요.

    - 병합하지 마시라고요. ㅠㅠ

    - 정 하고 싶으면 글 50만개, 댓글 500만개 정도 추천기록 더미데이터를 만들어 넣어놓고 실제로 테스트해 보세요.

    - 정석대로 구현한다면 추천이 발생할 때마다 트리거를 사용해서 별도의 테이블에 모든 추천기록을 쌓아야 합니다. 이걸 단순히 시간역순으로 정렬해서 가져오고, documents, comments 테이블을 조인해서 해당 문서와 댓글까지 한 번에 가져오는 거죠. 별도의 테이블을 사용한다면 댓글을 글마다 묶어서 보여주는 등, 훨씬 복잡한 기능도 무리 없이 구현 가능합니다.

  • profile profile
    답변 감사합니다.
    마지막줄에 말씀하신 방식으로 해야 하겠군요.
    새 테이블로 기존의 추천 로그 기록을 가져오고, 트리거로 신규 추천도 가져오고요.
    기존 테이블로만 어떻게 해보려 했는데 역시 무리한 시도였나봐요ㅜㅜ
  • profile

    게시글, 댓글 관계 없이 추천 기록을 날짜순으로 가지고 오고 싶으신거지요?
    저라면 애초에 추천 기록을 한 테이블에 전부 집어넣고 쓸거 같습니다.
    라이믹스에서 구현하려면 추천/추천취소 이벤트에 트리거를 달아서 구현해주어야 하겠지만요.

     

    이런 방법이 아니라면 그~나마 2번이 나을 것 같은데,

    추천이 매일 20개가 있는데, 오늘 댓글 추천이 빠진 상태라면, 평생 댓글 추천 기록을 보지 못할 것 같아요.

    (게시글 추천은 13일까지 모두 20개, 댓글 추천은 12월 12일까지만 20개씩.)

    ---
    쓰고 나서 보니 제가 한말을 기진곰님께서 이미 다 해 주셨네요ㅎㅎ;

  • profile profile

    감사합니다 😀
    상황이 더 구체화되는 느낌입니다!

    트리거는 문서/댓글 삭제 때에도 추천 기록 삭제가 되도록 해야 하겠고,
    다행히 문서 추천 기록과 댓글 추천 기록은 링크를 만들어놔서 url 파라미터에 따라 따로 불러오는 방식으로 적용은 해놓았습니다. (그나마 요건 잘 한 거 같아요ㅋㅋ)

  • profile profile
    기존 데이터들을 이후 추가될 별도 테이블에 복사해주는 버튼을 관리자 페이지 등에 추가해주면 금상첨화일거 같아요.
  • profile profile
    넵! 신규 테이블을 만들게 되면 저한텐 그게 관건일 거 같아요. 한번도 안 해본 거여서요ㅎㅎㅎ