ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [MongoDB] 4. 집계 연산 질의(Map Reduce)
    CSE/MongoDB 2015. 6. 13. 11:43
    4. 집계 연산 질의

    집계 연산을 수행하기 앞서, 예제 데이터를 생성해야 한다. 순서는 아래와 같다.
     1. 예제 데이터 생성하기(generate_date.php)
     2. 작성자별 아티클 수 세기
     3. 태그 클라우드 생성하기
     4. 작성자별 평균 평가 점수 계산하기
     5. 아티클의 유일한 카테고리 열거하기
     




    1. 예제 데이터 생성하기
     본 예제는 첨부된 파일을 받아서 실행하여, 데이터를 생성한다.




     생성된 데이터를 mongo shell에서 확인한다.
      use myblogsite
      db.sample_articles.find()
     

     

      




    위와 같이 데이터가 보이면 성공!



    맵리듀스(Map Reduce)는 데이터 처리를 위한 Design Pattern이다. 맵리듀스 이면의 사상은 큰 과업을 좀 더 작은 과업으로 분해하는 방법이다. 하위 과업은 독자적으로 수행한다. 쪼개진 하위 과업의 결과가 합쳐져 최종 결과를 생성한다. 두 단계로 나뉘어 진다.
     - Map 단계: 과업을 좀 더 작은 하위 과업으로 분해하고 중간 결과를 생성하기 위해 하위 과업 수행
     - Reduce 단계: 중간 결과를 결합해 최종 결과물을 만들어낸다.

    맵리듀스 참고 자료






     2. 작성자별 아티클 수 세기
      1) 구동된 mongo shell에 use myblogsite 입력
      2) var map = function() { emit(this.author, 1); }
      3) var reduce = function(key, values) {
            var count = 0;
            for (var i = 0; i < values.length; i++) { 
      count += values[i];
            }
            return count;
          };
       4) db.runCommand({
             mapreduce: 'sample_articles',
             map: map,
             reduce: reduce,
             out: 'articles_per_author'
           })
        
        5) 위에까지 순차적으로 에러없이 입력한 뒤, db.articles_per.author.find()로 맵리듀스 연산 결과를 확인한다.


     






    본 방식은 SQL의 질의는 
     SELECT COUNT(id) FROM sample_articles GROUP BY author 수행방식과 유사하다.

     map 함수 정의
      map 함수는 Collection에 들어있는 Document를 받아 새로운 Key / Value 쌍 집합을 만든다. 본 예시에서 정의한 map함수에서 Key는 document를 작성한 작성자 이름이며 Value는 정수 1을 담은 배열인 Key / Value 쌍을 생성한다. 매번 Map이 Key와 동일한 document를 만날 때마다, Value 배열에 단순히 1을 더한다.
     


     reduce 함수 정의
      map 함수가 emit()으로 내보내는 key /value 쌍은 reduce 함수로 들어간다. reduce 함수는 각 작성자를 받아 값 배열에서 1의 수를 더해 전체 카운트를 구한다.







     3. 태그 클라우드 생성하기
     PHP내에서 맵리듀스를 수행해보도록 하겠다. 먼저 tagcloud.php를 생성하여 아래 코드를 입력한다.

    tagcloug.php
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    <?php
    require("dbconnection.php");
    $mongo = DBConnection::instantiate();
    $db = $mongo->database;
     
    // map 함수 정의(Mongo)
    $map = new MongoCode("function() {".
    "for (var i = 0; i < this.tags.length; i++) {".
    "emit(this.tags[i], 1);".
    "}".
    "}"
    );
     
    // reduce 함수 정의(Mongo) 
    $reduce = new MongoCode("function(key, values) {".
    "var count =0;".
    "for (var i = 0; i < values.length; i++) {".
    "count += values[i];".
    "}".
    "return count;".
    "}"
    );
     
    // map과 reduce 함수를 실행, 결과를 tagcount라는 Collection에 저장
    $command = array
    'mapreduce' => 'sample_articles',
    'map' => $map,
    'reduce' => $reduce,
    'out' => 'tagcount'
    );
     
    $db->command($command);
     
    // 모든 태그를 배열에 올려 빈도순으로 정렬
    $tags = iterator_to_array($db->selectCollection('tagcount')
    ->find()
    ->sort(array('value' => -1)));
     
    // 가장 높은 빈도로 출현하는 태그를 찾기 위한 함수
    function getBiggestTag($tags) {
    // 배열을 재설정해 첫 항목을 가르킴
    reset($tags);
     
    // 연관 배열의 첫 키를 얻음
    $firstKey = key($tags);
     
    return (int)$tags[$firstKey]['value'];
    }
     
    $biggestTag = getBiggestTag($tags);
     
    foreach($tags as &$tag) {
    $weight = floor(($tag['value'] / $biggestTag) * 100);
     
    switch ($weight) {
    case ($weight < 10):
    $tag['class'] = 'class1';
    break;
    case (10 <= $weight && $weight < 20):
    $tag['class'] = 'class2';
    break;
    case (20 <= $weight && $weight < 30):
    $tag['class'] = 'class3';
    break;
    case (30 <= $weight && $weight < 40):
    $tag['class'] = 'class4';
    break;
    case (40 <= $weight && $weight < 50):
    $tag['class'] = 'class5';
    break;
    case (50 <= $weight && $weight < 60):
    $tag['class'] = 'class6';
    break;
    case (60 <= $weight && $weight < 70):
    $tag['class'] = 'class7';
    break;
    case (70 <= $weight && $weight < 80):
    $tag['class'] = 'class8';
    break;
    case (80 <= $weight && $weight < 90):
    $tag['class'] = 'class9';
    break;
    case ($weight > 90):
    $tag['class'] = 'class10';
    break;
    }
    }
    ?>
     
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" lang="en">
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
            <link rel="stylesheet" href="style.css"/>
            <title> Tag Cloud </title>
        </head>
        <body>
            <div id="contentarea">
                <div id="innercontentarea">
                    <h1> Tag Cloud </h1>
                    <ul id="tagcloud">
                        <?php foreach($tags as $tag):?>
                            <li>
                                <a href="#" class="<?php echo $tag['class']; ?>">
                                <?php echo $tag['_id'];?></a>
                            </li>
                        <?php endforeach;?>
                    </ul>
                </div<!--End of div innercontentarea -->
            </div>
        </body>
    </html>
    cs






     다음으로 첨부된 css 파일을 다운 받아서 적용시키자.
     그 다음, 웹에서 tagcloud.php에 접속하자. 아래와 같이 나오는데 아마 다르게 나올 것이다. 랜덤으로 생성한 값이 본 예제와 일치하지 않기 때문.










    4. 작성자별 평균 평가 점수 계산하기
     group() 메소드를 통하여 평균 평가 점수를 작성자별로 출력하겠다.
    먼저 avg_rating.php를 작성한다. 아래와 같이

    avg_rating.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    <?php
    require('dbconnection.php');
     
    $mongo = DBConnection::instantiate();
    $collection = $mongo->getCollection('sample_articles');
     
    $key = array('author' => 1);
     
    // 집계 연산 카운터와 전체 평가 점수를 0으로 설정
    $initial = array('count' => 0'total_rating' => 0);
     
    // reduce 함수 - 카운터를 1 증가시키고 평가점수를 더함
    $reduce = "function(obj, counter) { counter.count++; counter.total_rating += obj.rating; } ";
     
    // 평균 평가점수를 찾는다
    $finalize = "function(counter) { counter.avg_rating = Math.round(counter.total_rating / counter.count); } ";
    $condition = array('published_at' => array('$gte' => new MongoDate(strtotime(' day'))));
     
    $result = $collection->group($key$initialnew MongoCode($reduce),
    array(
    'finalize' => new MongoCode($finalize),
    'condition' => $condition
    )
    );
     
    // echo json_encode($result);
    ?>
     
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
        <head>
            <title>Author Rating</title
            <link rel="stylesheet" href="style.css"/>
     
        </head>
        <body>
            <div id="contentarea">
                <div id="innercontentarea">
                    <h1>Authors' Ratings</h1>
                    <table class="table_list" cellspacing="0" cellpadding="0">
                        <thead>
                            <tr>
                                <th width="50%">Author</th>
                                <th width="24%">Articles</th>
                                <th width="*">Average Rating</th>
                            </tr>
                        </thead>
                        <tbody>
                        <?php foreach($result['retval'as $obj):?>
                            <tr>
                                <td><?php echo $obj['author'];?></td>
                                <td><?php echo $obj['count'];?></td>
                                <td><?php echo $obj['avg_rating'];?></td>
                            </tr>
                        <?php endforeach;?>
                        </tbody>
                    </table>
                </div>
            </div>
        </body>
    </html>
    cs

     




    페이지를 열어 확인한다. 아래와 비슷하게 나왔는지 확인










    위 과정을 설명하자면, sample_articles 컬렉션을 표현하는 MongoCollection에 존재하는 group() 메소드를 호출했다.
    매개변수를 살펴보면, 
      첫째로, 작성자 이름(author)으로 아티클을 Group짓기 위해 Key parameter로 array('author' => 1)을 넘겼다. => $key
      둘째로, 집계 연산 카운터(counter)의 count와 total_rating을 0으로 초기화했다. => $initial
      셋째로, $reduce는 다큐먼트를 순회하며, 1씩 집계 연산 카운터의 count필드를 증가시키며, 이 카운터의 total_rating 필드에 현재 다큐먼트의 평가 점수를 더한다. 
      넷째로, $finalize는 카운터 수로 전체 평가 점수를 ㄴㅏ눈 다음에 몫을 반올림하는 방법으로 평균을 계산한다.
      다섯쨰로, $condition은 그룹 연산을 수행하기 위해 넘겨주는 parameter이다.
     

    5. 아티클의 유일한(distinct) 카테고리 열거하기



    마지막으로 distinct에 대해 알아보자.

    distinct.php

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <?php
    require('dbconnection.php');
     
    $mongo = DBConnection::instantiate();
    $db = $mongo->database;
    $result = $db->command(array('distinct' => 'sample_articles''key' => 'category'));
    ?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
        <head>
            <title> Categories </title
            <link rel="stylesheet" href="style.css"/>
     
        </head>
        <body>
            <div id="contentarea">
                <div id="innercontentarea">
                    <h1> Distinct Categories </h1>
                    <ul>
                    <?php foreach($result['values'as $value):?>
                        <li><?php echo $value;?></li>
                    <?php endforeach;?>
                    </ul>
                </div>
            </div>
        </body>
    </html>
    cs




    바로 웹에서 수행한다.






    각 아티클 다큐먼트의 카테고리 필드에 속한 유일한 값을 모두 열거하는 스크립트이다. 

    mongo shell에서 distinct()를 써보도록 하겠다.
    사용법은 아래와 같다.
       db.collectionName.distinct(Key

     본 예제에서는 db.sample_artilces.distinct('category')

    이것으로 4장을 마무리하겠습니다.






     


    댓글

Designed by Tistory.