MapReduceビューを使用したCouchbaseのクエリ
1. 概要
このチュートリアルでは、いくつかの簡単なMapReduceビューを紹介し、 Couchbase JavaSDKを使用してそれらをクエリする方法を示します。
2. Mavenの依存関係
MavenプロジェクトでCouchbaseを操作するには、CouchbaseSDKをpom.xmlにインポートします。
<dependency>
<groupId>com.couchbase.client</groupId>
<artifactId>java-client</artifactId>
<version>2.4.0</version>
</dependency>
最新バージョンはMavenCentralにあります。
3. MapReduceビュー
Couchbaseでは、MapReduceビューは、データバケットのクエリに使用できるインデックスの一種です。 これは、JavaScriptのmap関数とオプションのreduce関数を使用して定義されます。
3.1. map関数
map 関数は、各ドキュメントに対して1回実行されます。 ビューが作成されると、 map 関数がバケット内の各ドキュメントに対して1回実行され、結果がバケットに保存されます。
ビューが作成されると、 map 関数は、ビューを段階的に更新するために、新しく挿入または更新されたドキュメントに対してのみ実行されます。
map 関数の結果はデータバケットに保存されるため、ビューに対するクエリのレイテンシは低くなります。
typeフィールドがと等しいバケット内のすべてのドキュメントのnameフィールドにインデックスを作成するmap関数の例を見てみましょう。 「StudentGrade」:
function (doc, meta) {
if(doc.type == "StudentGrade" && doc.name) {
emit(doc.name, null);
}
}
emit 関数は、インデックスキー(最初のパラメーター)に格納するデータフィールドと、インデックス付きドキュメントに関連付ける値(2番目のパラメーター)をCouchbaseに指示します。
この場合、ドキュメントnameプロパティのみをインデックスキーに保存しています。 また、特定の値を各エントリに関連付けることには関心がないため、値パラメータとしてnullを渡します。
Couchbaseはビューを処理するときに、 map 関数によって発行されたキーのインデックスを作成し、各キーをそのキーが発行されたすべてのドキュメントに関連付けます。
たとえば、3つのドキュメントの name プロパティが“ John Doe” に設定されている場合、インデックスキー“ John Doe”はこれらの3つのドキュメントに関連付けられます。 。
3.2. reduce関数
reduce 関数は、map関数の結果を使用して集計計算を実行するために使用されます。 Couchbase Admin UIは、組み込みのreduce関数“_count”、 “_ sum”、および“_stats”をに適用する簡単な方法を提供します。 X150X]map関数。
より複雑な集計のために、独自のreduce関数を作成することもできます。 チュートリアルの後半で、組み込みのreduce関数の使用例を示します。
4. ビューとクエリの操作
4.1. ビューを整理する
ビューは、バケットごとに1つ以上の設計ドキュメントに編成されます。 理論的には、設計ドキュメントごとのビューの数に制限はありません。 ただし、最適なパフォーマンスを得るには、各設計ドキュメントを10ビュー未満に制限することをお勧めします。
デザインドキュメント内に最初にビューを作成するとき、Couchbaseはそれを開発ビューとして指定します。 development ビューに対してクエリを実行して、その機能をテストできます。 ビューに満足したら、設計ドキュメントを公開し、ビューは本番ビューになります。
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 値の範囲が含まれるため、各キーを[
ビュー「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つの最高の数学の成績(同点を除く)を見つけるために、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 =クレジット時間あたり3ポイント、クレジット時間あたりC = 2ポイント、クレジット時間あたりD = 1ポイント)。
平均値を計算するための組み込みのreduce関数がないため、2つのビューからの出力を組み合わせてGPAを計算します。
各学生が試行した単位時間数を合計する「sumHoursByStudent」ビューがすでにあります。 ここで、各生徒が獲得した成績ポイントの総数が必要です。
「sumGradePointsByStudent」というビューを作成して、受講した各コースで獲得した成績ポイントの数を計算してみましょう。 組み込みの“ _ sum” 関数を使用して、次のmap関数を減らします。
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ビューを作成する方法と、ビューに対してクエリを作成して実行し、結果を抽出する方法を示しました。
このチュートリアルで紹介するコードは、GitHubプロジェクトにあります。
MapReduceビューの詳細と、Java でクエリを実行する方法については、公式のCouchbase開発者向けドキュメントサイトをご覧ください。