Collapse AllExpand All

4.10. 카테고리 그룹핑

검색 대상이 되는 sample 테이블은 카테고리 코드 (category_cd)와 카테고리명 (category_nm) 필드를 가지고 있다. 카테고리 코드 필드는 CATEGORY1부터 CATEGORY6까지의 값을 가지며 그에 해당하는 카테고리명 필드 값은 각각 "오늘", "사회", "정보과학", "연예스포츠", "정치경제", "기타"이다. 이제 검색 결과를 이 카테고리 코드로 그룹핑 하는 예제 페이지를 작성한다.

화면은 아래와 같이 각 카테고리가 탭으로 표시되는 형식이다. 탭 제목에 카테고리명과 각 카테고리에 속한 검색 레코드 개수가 함께 표시된다.

그림 C.13. 카테고리 그룹핑

카테고리 그룹핑

이를 위해서는 두 번의 검색이 필요하다. 먼저 카테고리 그룹별 건수를 구하기 위해 GROUP BY를 이용한 검색이 필요하고 다음 실제 키워드 등 검색 조건에 따른 검색이 필요하다.

GROUP BY를 이용한 검색과 실제 검색 모두 검색 조건은 같다. 따라서 검색 조건을 먼저 만들고 이 검색 조건을 이용하여 GROUP BY 및 실제 검색을 수행할 SearchQuery를 생성하도록 SearchQueryBuilder 를 수정한다.

1.	<%!public static class SearchQueryBuilder {
2.	  private String query;
3.	  private QueryBuilder qb;  // 검색 조건을 저장하기 위해 사용
4.	
5.	  public SearchQueryBuilder(String query) {
6.	    this.query = query;
7.	    this.qb = new QueryBuilder();
8.	  }
9.	  
  // 검색 조건을 구한다.
10.	  public void prepare(HttpServletRequest request) {
11.		qb.whereColumnEquals("text_idx", query, "allwordthruindex");
12.		String dateRange = RequestUtils.getParameter
                        (request, "date-range");
13.		if ("r".equals(dateRange)) {
14.		  qb.whereColumnInDateRange("regdate", 
15.	          RequestUtils.getParameter(request, "date-from"), 
16.	          RequestUtils.getParameter(request, "date-to"));
17.	    } else {
18.		  qb.whereColumnInDateRange("regdate", dateRange);
19.	    }
20.	  }
21.	
  // GROUP BY 검색 시 사용할 쿼리를 구한다.
22.	  public SearchQuery getGroupByQuery() {
23.		SearchQuery sq = new SearchQuery("sample", query);
24.		sq.setWhereClause(qb.getWhereClause());
    // GROUP BY 구문을 지정한다.
25.		sq.setSortingClause("group by category_cd order by category_cd");
    // GROUP BY 구문에 따라 그룹별 개수를 가져오도록 설정한다.
26.		sq.setGroupBy(true);
27.		return sq;
28.	  }
29.	
  // 실제 검색에 사용할 쿼리를 구한다.
30.	  public SearchQuery getSearchQuery(HttpServletRequest request) {
31.		SearchQuery sq = new SearchQuery("sample", query);
32.		sq.setLimit(RequestUtils.getParameterInt(request, "rpp", 10));
33.		sq.setOffset(RequestUtils.getParameterInt(request, "p", 0) 
                              * sq.getLimit());
34.	    sq.setWhereClause(qb.getWhereClause());
35.		qb.orderBy(RequestUtils.getParameterValues(request, "sort"));
36.	    sq.setSortingClause(qb.getSortingClause());
37.	    return sq;
38.	  }
39.	}
40.	%>

이제 검색을 수행하는 JSP 코드를 수정하자.

1.	…
2.	CrzClient crzclient = new CrzClient("127.0.0.1", 9577);
3.	SearchQueryBuilder builder = new SearchQueryBuilder(query);
// 먼저 검색 조건을 구한다.
4.	builder.prepare(request);
// 다음 GROUP BY 검색을 수행하고 결과를 groups라는 세션 애트리뷰트에 저장한다.
5.	SearchResultSet groups = crzclient.search(builder.getGroupByQuery());
6.	session.setAttribute("groups", groups);
// 마지막으로 실제 검색을 수행한다.
7.	SearchQuery sq = builder.getSearchQuery(request);
8.	SearchResultSet srs = crzclient.search(sq);
9.	pageContext.setAttribute("sq", sq);
10.	pageContext.setAttribute("srs", srs);
11.	…

Groups 애트리뷰트에 저장된 그룹 별 검색 개수를 표시하는 코드는 아래와 같다.

12.	<c:if test="${not empty groups}">
13.	<ul id="categories" class="nav nav-tabs">
14.	  <li class="active"><a href="#">
15.	전체 (<fmt:formatNumber value="${groups.totalCount}" 
                             groupingUsed="true"/>)
16.	  </a></li>
17.	  <c:forEach var="group" items="${groups.rows}">
18.	  <li><a href="#${group.key}">
19.	<fmt:message key="${group.key}"/> (<fmt:formatNumber 
                 value="${group.size}" groupingUsed="true"/>)
20.	  </a></li>
21.	  </c:forEach>
22.	</ul>
23.	</c:if>

각 그룹에 속한 검색 결과의 총합은 ${groups.totalCount}에 저장되며, 각 그룹의 대표 값과 그룹에 속한 검색 결과의 개수는 각각 ${group.key}와 ${group.size}로 구할 수 있다.

이제 각 그룹 탭을 누르면 해당 그룹에 속한 검색 결과만 필터링 하는 기능을 추가해보자. 그룹 탭을 클릭할 경우에는 그룹별 건수를 다시 구할 필요가 없고 category_cd 관련 검색 조건만 바꿔 새로 검색하면 된다. 이를 위해 categorize와 category라는 파라미터를 추가한다. Categorize 파라미터는 GROUP BY 검색을 수행할지 여부를 나타내고 category 파라미터는 검색할 카테고리를 나타낸다.

1.	<form name="search-form" method="post" class="form-inline" 
         role="form">
2.	..
3.	<input type="hidden" id="category" name="category" 
          value="${param['category']}"/>
4.	<input type="hidden" id="categorize" name="categorize" 
          value="true"/>
5.	</form>

이제 자바스크립트를 수정한다.

1.	$("form[name=search-form]").submit(function(event) {
  // 새로 카테고리 그룹핑할 경우에는 전체 그룹을 대상으로 한다.
2. if ($("#categorize").val() == "true") $("#category").val("");
3.	  …
4.	}
5.	…
// 현재 필터링된 그룹탭을 선택한다. 
6.	$("#categories").find("a[href=#"+$("#category").val()+"]")
                   .parent().addClass("active");
7.	$("#categories").on("click", "a", function() {
  // 카테고리 필터링 시에는 다시 그룹 별 건수를 구할 필요 없다.
8.   $("#categorize").val(false);
9.	  $("#category").val($(this).attr("href").substring(1));
10.	  $("form[name=search-form]").submit();
11.	});
12.	..
13.	$("#pagination").pagination(${srs.totalCount}, {
14.	  callback: function(p) {
    // 페이지 이동 시에도 다시 그룹 별 건수를 구할 필요 없다.
15. $("#categorize").val(false);
16.		…
17.	  }
18.	});

마지막으로 검색을 수행하는 JSP와 SearchQueryBuilder를 수정한다.

1.	…
2.  <%-- categorize 파라미터가 true일 경우에만 그룹 별 건수를 구한다. --%>
if (RequestUtils.getParameterBool(request, "categorize", true)) {
3.	  SearchResultSet groups = crzclient.search(builder.getGroupByQuery());
4.	  session.setAttribute("groups", groups);
5.	}
6.	..
7.	<%!public static class SearchQueryBuilder {
8.	…
9.	  public SearchQuery getSearchQuery(HttpServletRequest request) {
10.	    …
11.	 String category = RequestUtils.getParameter(request, "category");
   <%-- 카테고리 코드로 필터링하는 검색 조건을 추가한다. 전체일 경우 
         (category_cd가 “”일 경우에는 검색 조건에 추가되지 않는다.) --%>
12.	   qb.whereColumnEquals("category_cd", category);
13.	   sq.setWhereClause(qb.getWhereClause());
14.	   …
15.	  }
16.	}
17.	%>

이제 그룹별 필터링 기능이 추가된 페이지를 실행해 보자. 아래의 예에서 "사회" 탭을 클릭 시 총 145건의 검색 결과 중 "사회" 카테고리로 필터링 된 2건의 검색 결과만 표시되는 것을 확인할 수 있다.

결과

그림 C.14. 그룹 별 필터링 기능이 추가된 결과

그룹 별 필터링 기능이 추가된 결과