MapReduceビューでCouchbaseに問い合わせる

1概要

このチュートリアルでは、簡単なMapReduceビューをいくつか紹介し、https://developer.couchbase.com/documentation/server/current/sdk/java/start -using-sdk.html[Couchbase Java SDKを使用してそれらをクエリする方法を示します。]。

2 Mavenの依存関係

MavenプロジェクトでCouchbaseを使用するには、Couchbase SDKを pom.xml にインポートします。

<dependency>
    <groupId>com.couchbase.client</groupId>
    <artifactId>java-client</artifactId>
    <version>2.4.0</version>
</dependency>

最新版はhttps://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22com.couchbase.client%22%20AND%20a%3A%22java-client%22[Maven Central]。

3 MapReduceビュー

Couchbaseでは、MapReduceビューはデータバケットのクエリに使用できるインデックスの一種です。それはJavaScriptの map 関数とオプションの reduce 関数を使って定義されます。

3.1. map 関数

map 関数は各文書に対して1回実行されます。ビューが作成されると、バケット内の各ドキュメントに対して map 関数が1回実行され、結果がバケットに格納されます。

ビューが作成されると、ビューを徐々に更新するために、 map 関数は新しく挿入または更新されたドキュメントに対してのみ実行されます。

map 関数の結果はデー​​タバケットに格納されるため、ビューに対するクエリは待ち時間が短くなります。

type フィールドが “ StudentGrade” と等しいバケット内のすべてのドキュメントの name フィールドにインデックスを作成する map 関数の例を見てみましょう。

function (doc, meta) {
    if(doc.type == "StudentGrade" && doc.name) {
        emit(doc.name, null);
    }
}

emit 関数は、インデックスキーに格納するデータフィールド(最初のパラメータ)とインデックス付きドキュメントに関連付ける値(2番目のパラメータ)をCouchbaseに指示します。

この場合は、document name プロパティのみをインデックスキーに格納しています。また、各エントリに特定の値を関連付けることには関心がないため、valueパラメータとして null を渡します。

Couchbaseはビューを処理すると、 map 関数によって発行されたキーのインデックスを作成し、各キーをそのキーが発行されたすべてのドキュメントに関連付けます。

たとえば、3つのドキュメントの name プロパティが "John Doe" に設定されている場合、インデックスキー "John Doe" がそれら3つのドキュメントに関連付けられます。

3.2. reduce 関数

reduce 関数は、 map 関数の結果を使用して集計計算を実行するために使用されます。 Couchbase管理UIは、 map 関数に組み込みの reduce 関数 count”、“ sum”、 および stats” __を適用する簡単な方法を提供します。

もっと複雑な集約のためにあなた自身の reduce 関数を書くこともできます。チュートリアルの後半で、組み込みの reduce 関数の使用例を見ます。

4ビューとクエリを操作する

4.1. ビューを整理する

ビューは、バケットごとに1つ以上のデザインドキュメントにまとめられます。理論的には、デザインドキュメントあたりのビュー数に制限はありません。

ただし、最適なパフォーマンスを得るには、各デザイン文書を10ビュー未満に制限することをお勧めします。

デザインドキュメント内で最初にビューを作成したとき、Couchbaseはそれを「開発ビュー」として指定します。 development ビューに対してクエリを実行してその機能をテストできます。ビューに満足したら、デザインドキュメントを公開し、そのビューが production viewになります。

4.2. クエリの構築

Couchbaseのビューに対してクエリを作成するには、デザインドキュメントの名前とビューの名前を指定して ViewQuery オブジェクトを作成する必要があります。

ViewQuery query = ViewQuery.from("design-document-name", "view-name");

実行すると、このクエリはビューのすべての行を返します。後のセクションで、キー値に基づいて結果セットを制限する方法について説明します。

開発ビューに対してクエリを作成するには、クエリの作成時に development() メソッドを適用します。

ViewQuery query
  = ViewQuery.from("design-doc-name", "view-name").development();

4.3. クエリの実行

ViewQuery オブジェクトを取得したら、クエリを実行して ViewResult を取得できます。

ViewResult result = bucket.query(query);

4.4. クエリ結果の処理

これで ViewResult ができたので、行を繰り返し処理してドキュメントIDやコンテンツを取得できます。

for(ViewRow row : result.allRows()) {
    JsonDocument doc = row.document();
    String id = doc.id();
    String json = doc.content().toString();
}

** 5サンプルアプリケーション

チュートリアルの残りの部分では、0から100の範囲に制限された成績で、以下のフォーマットを持つ一連の学生成績文書のMapReduceビューとクエリを作成します。

{
    "type": "StudentGrade",
    "name": "John Doe",
    "course": "History",
    "hours": 3,
    "grade": 95
}

これらのドキュメントを“ baeldung-tutorial ”バケットに保存し、すべてのビューを“ studentGrades ”という名前のデザインドキュメントに保存します。バケットを開くために必要なコードを見てみましょう。

Bucket bucket = CouchbaseCluster.create("127.0.0.1")
  .openBucket("baeldung-tutorial");

6. 完全一致クエリ

特定のコースまたは一連のコースのすべての学年を見つけたいとします。次の map 関数を使用して、 " findByCourse "というビューを書きましょう。

function (doc, meta) {
    if(doc.type == "StudentGrade" && doc.course && doc.grade) {
        emit(doc.course, null);
    }
}

この単純なビューでは、 course フィールドを発行するだけでよいことに注意してください。

6.1. 単一キーでのマッチング

歴史コースのすべての成績を見つけるために、 key メソッドを基本クエリに適用します。

ViewQuery query
  = ViewQuery.from("studentGrades", "findByCourse").key("History");

6.2. 複数のキーでのマッチング

数学と科学のコースのすべての成績を見つけたい場合は、キー値の配列を渡して keys メソッドを基本クエリに適用できます。

ViewQuery query = ViewQuery
  .from("studentGrades", "findByCourse")
  .keys(JsonArray.from("Math", "Science"));

7. 範囲クエリ

1つ以上のフィールドの値の範囲を含むドキュメントをクエリするには、目的のフィールドを出力するビューが必要です。また、クエリに対して下限または上限(あるいはその両方)を指定する必要があります。

単一のフィールドと複数のフィールドを含む範囲クエリを実行する方法を見てみましょう。

7.1. 単一フィールドを含むクエリ

course フィールドの値に関係なく、 grade 値の範囲を持つすべてのドキュメントを見つけるには、 grade フィールドのみを出力するビューが必要です。 “ findByGrade ”ビュー用の map 関数を書きましょう。

function (doc, meta) {
    if(doc.type == "StudentGrade" && doc.grade) {
        emit(doc.grade, null);
    }
}

このビューを使用してJavaでクエリを作成し、「B」の文字グレード(80から89まで)に相当するすべてのグレードを見つけます。

ViewQuery query = ViewQuery.from("studentGrades", "findByGrade")
  .startKey(80)
  .endKey(89)
  .inclusiveEnd(true);

範囲照会の開始キー値は常に​​包含的として扱われることに注意してください。

そして、すべての等級が整数であることがわかっているならば、以下の質問は同じ結果をもたらすでしょう:

ViewQuery query = ViewQuery.from("studentGrades", "findByGrade")
  .startKey(80)
  .endKey(90)
  .inclusiveEnd(false);

すべての「A」グレード(90以上)を見つけるには、下限を指定するだけで済みます。

ViewQuery query = ViewQuery
  .from("studentGrades", "findByGrade")
  .startKey(90);

そして、すべての不合格の成績(60以下)を見つけるには、上限を指定するだけです。

ViewQuery query = ViewQuery
  .from("studentGrades", "findByGrade")
  .endKey(60)
  .inclusiveEnd(false);

7.2. 複数のフィールドを含むクエリ

ここで、学年が特定の範囲に入る特定のコースのすべての学生を見つけたいとします。このクエリは course フィールドと grade フィールドの両方を発行する新しいビューを必要とします。

複数フィールドビューでは、各インデックスキーは値の配列として発行されます。

このクエリは course の固定値と grade 値の範囲を含むので、各キーを[ course grade ]の形式の配列として発行するmap関数を作成します。

ビュー“ findByCourseAndGrade ”の map 関数を見てみましょう。

function (doc, meta) {
    if(doc.type == "StudentGrade" && doc.course && doc.grade) {
        emit([doc.course, doc.grade], null);
    }
}

このビューがCouchbaseに読み込まれると、インデックスエントリは course grade でソートされます。 「 findByCourseAndGrade 」ビューのキーのサブセットは、自然なソート順で表示されています。

----["History", 80]["History", 90]["History", 94]["Math", 82]["Math", 88]["Math", 97]["Science", 78]["Science", 86]["Science", 92]----

このビューのキーは配列なので、このビューに対する範囲クエリの下限と上限を指定するときにもこの形式の配列を使用します。

これは、数学コースで「B」の成績(80から89)を取得したすべての生徒を見つけるには、下限を次のように設定することを意味します。

----["Math", 80]----

そして上限は:

----["Math", 89]----

範囲クエリをJavaで書いてみましょう。

ViewQuery query = ViewQuery
  .from("studentGrades", "findByCourseAndGrade")
  .startKey(JsonArray.from("Math", 80))
  .endKey(JsonArray.from("Math", 89))
  .inclusiveEnd(true);

数学で「A」グレード(90以上)を取得したすべての学生を見つけたい場合は、次のように書きます。

ViewQuery query = ViewQuery
  .from("studentGrades", "findByCourseAndGrade")
  .startKey(JsonArray.from("Math", 90))
  .endKey(JsonArray.from("Math", 100));

コース値を " Math "に固定しているので、可能な限り最高の grade 値を持つ上限を含める必要があります。

それ以外の場合、結果セットには、 course の値が辞書式に " Math "よりも大きいすべての文書も含まれます。

そして、すべての不合格の数学の成績(60以下)を見つけるために:

ViewQuery query = ViewQuery
  .from("studentGrades", "findByCourseAndGrade")
  .startKey(JsonArray.from("Math", 0))
  .endKey(JsonArray.from("Math", 60))
  .inclusiveEnd(false);

前の例と同じように、可能な限り低い等級で下限を指定する必要があります。そうでなければ、結果セットは course の値が辞書式に " Math "よりも小さいすべてのグレードも含みます。

最後に、5つの最も高いMathの成績を見つけるために(どんなつながりも除けば)、Couchbaseに降順ソートを実行し、結果セットのサイズを制限するように指示できます。

ViewQuery query = ViewQuery
  .from("studentGrades", "findByCourseAndGrade")
  .descending()
  .startKey(JsonArray.from("Math", 100))
  .endKey(JsonArray.from("Math", 0))
  .inclusiveEnd(true)
  .limit(5);

降順のソートを実行すると、Couchbaseは limit を適用する前にソートを適用するため、 startKey endKey の値は逆になります。

8集計クエリ

MapReduceビューの最大の長所は、大きなデータセットに対して集約クエリを実行するのに非常に効率的であることです。たとえば、学生の成績データセットでは、次の集計を簡単に計算できます。

  • 各コースの生徒数

  • 各学生の単位時間の合計

  • 全コースにわたる各学生の成績平均点

組み込みの reduce 関数を使用して、ビューを作成し、これらの各計算に対するクエリを実行しましょう。

8.1. count() 関数を使用する

まず、各コースの生徒数を数えるためのビューの map 関数を書きましょう。

function (doc, meta) {
    if(doc.type == "StudentGrade" && doc.course && doc.name) {
        emit([doc.course, doc.name], null);
    }
}

このビューを " countStudentsByCourse "と呼び、組み込みの " " count " 関数を使用することを指定します。単純なカウントを実行しているだけなので、各エントリの値として null__を発行することもできます。

各コースの生徒数を数えるには:

ViewQuery query = ViewQuery
  .from("studentGrades", "countStudentsByCourse")
  .reduce()
  .groupLevel(1);

集約クエリからデータを抽出することは、これまで見てきたものとは異なります。結果の各行に一致するCouchbaseドキュメントを抽出する代わりに、集計キーと結果を抽出します。

クエリを実行し、カウントを java.util.Map に抽出しましょう。

ViewResult result = bucket.query(query);
Map<String, Long> numStudentsByCourse = new HashMap<>();
for(ViewRow row : result.allRows()) {
    JsonArray keyArray = (JsonArray) row.key();
    String course = keyArray.getString(0);
    long count = Long.valueOf(row.value().toString());
    numStudentsByCourse.put(course, count);
}

8.2. sum() 関数を使用する

次に、受講した各生徒の単位時間の合計を計算するビューを書きましょう。このビューを " sumHoursByStudent "と呼び、組み込みの " " sum "__関数を使用することを指定します。

function (doc, meta) {
    if(doc.type == "StudentGrade"
         && doc.name
         && doc.course
         && doc.hours) {
        emit([doc.name, doc.course], doc.hours);
    }
}

" sum" 関数を適用するときは、各エントリについて合計する値(この場合はクレジット数)を emit__する必要があります。

各生徒の合計クレジット数を見つけるためのクエリを作成しましょう。

ViewQuery query = ViewQuery
  .from("studentGrades", "sumCreditsByStudent")
  .reduce()
  .groupLevel(1);

それでは、クエリを実行し、集計した合計を java.util.Map に抽出しましょう。

ViewResult result = bucket.query(query);
Map<String, Long> hoursByStudent = new HashMap<>();
for(ViewRow row : result.allRows()) {
    String name = (String) row.key();
    long sum = Long.valueOf(row.value().toString());
    hoursByStudent.put(name, sum);
}

8.3. 成績平均点の計算

得られた成績とコースの価値がある単位時間に基づく従来の成績評価基準スケールを使用して、すべてのコースで各生徒の成績平均点(GPA)を計算するとします(A =単位時間あたり4ポイント、B = 1クレジット時間あたり3ポイント、C = 1クレジット時間あたり2ポイント、およびD = 1クレジット時間あたり1ポイント。

平均値を計算するための組み込みの reduce 関数はありませんので、2つのビューからの出力を組み合わせてGPAを計算します。

各生徒が試行した単位時間数を合計する "sumHoursByStudent" ビューがすでにあります。今度は、各生徒が獲得した評点の合計数が必要です。

受講した各コースで獲得した成績ポイントの数を計算する “ sumGradePointsByStudent” というビューを作成しましょう。次の map 関数を減らすために、組み込みの sum” __関数を使用します。

function (doc, meta) {
    if(doc.type == "StudentGrade"
         && doc.name
         && doc.hours
         && doc.grade) {
        if(doc.grade >= 90) {
            emit(doc.name, 4** doc.hours);
        }
        else if(doc.grade >= 80) {
            emit(doc.name, 3** doc.hours);
        }
        else if(doc.grade >= 70) {
            emit(doc.name, 2** doc.hours);
        }
        else if(doc.grade >= 60) {
            emit(doc.name, doc.hours);
        }
        else {
            emit(doc.name, 0);
        }
    }
}

それでは、このビューをクエリして合計を java.util.Map に抽出しましょう。

ViewQuery query = ViewQuery.from(
  "studentGrades",
  "sumGradePointsByStudent")
  .reduce()
  .groupLevel(1);
ViewResult result = bucket.query(query);

Map<String, Long> gradePointsByStudent = new HashMap<>();
for(ViewRow row : result.allRows()) {
    String course = (String) row.key();
    long sum = Long.valueOf(row.value().toString());
    gradePointsByStudent.put(course, sum);
}

最後に、各生徒のGPAを計算するために2つの __Map __を結合しましょう。

Map<String, Float> result = new HashMap<>();
for(Entry<String, Long> creditHoursEntry : hoursByStudent.entrySet()) {
    String name = creditHoursEntry.getKey();
    long totalHours = creditHoursEntry.getValue();
    long totalGradePoints = gradePointsByStudent.get(name);
    result.put(name, ((float) totalGradePoints/totalHours));
}

9結論

Couchbaseで基本的なMapReduceビューを作成する方法、およびビューに対してクエリを作成して実行し、結果を抽出する方法を示しました。

このチュートリアルで提示されているコードはhttps://github.com/eugenp/tutorials/tree/master/couchbase[GitHubプロジェクト]にあります。

MapReduce views およびhttps://developer.couchbase.com/documentationの使い方の詳細については、こちらをご覧ください。 Javaでそれらを照会 [Couchbase開発者向けドキュメントサイト]