Couchbase mit MapReduce-Ansichten abfragen

Abfragen von Couchbase mit MapReduce-Ansichten

1. Überblick

In diesem Tutorial werden einige einfache MapReduce-Ansichten vorgestellt und gezeigt, wie sie mitCouchbase Java SDK abgefragt werden.

2. Maven-Abhängigkeit

Um mit Couchbase in einem Maven-Projekt zu arbeiten, importieren Sie das Couchbase-SDK in Ihrepom.xml:


    com.couchbase.client
    java-client
    2.4.0

Sie finden die neueste Version aufMaven Central.

3. MapReduce-Ansichten

In Couchbase ist eine MapReduce-Ansicht ein Indextyp, mit dem ein Daten-Bucket abgefragt werden kann. Es wird mit einer JavaScript-Funktionmapund einer optionalen Funktionreducedefiniert.

3.1. Diemap Funktion

Die Funktionmapwird einmal für jedes Dokument ausgeführt. Wenn die Ansicht erstellt wird, wird die Funktionmapeinmal für jedes Dokument im Bucket ausgeführt, und die Ergebnisse werden im Bucket gespeichert.

Sobald eine Ansicht erstellt wurde, wird die Funktionmapnur für neu eingefügte oder aktualisierte Dokumente ausgeführt, um die Ansicht schrittweise zu aktualisieren.

Da die Ergebnisse dermap-Funktion im Datenbereich gespeichert sind, weisen Abfragen für eine Ansicht geringe Latenzen auf.

Schauen wir uns ein Beispiel für einemap-Funktion an, die einen Index für dasname-Feld aller Dokumente im Bucket erstellt, derentype-Feld“StudentGrade” entspricht:

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

Die Funktionemit teilt Couchbase mit, welche Datenfelder im Indexschlüssel (erster Parameter) gespeichert werden sollen und welcher Wert (zweiter Parameter) dem indizierten Dokument zugeordnet werden soll.

In diesem Fall speichern wir nur die Eigenschaft des Dokumentsnameim Indexschlüssel. Und da wir nicht daran interessiert sind, jedem Eintrag einen bestimmten Wert zuzuordnen, übergeben wirnull als Wertparameter.

Während Couchbase die Ansicht verarbeitet, wird ein Index der Schlüssel erstellt, die von der Funktionmapausgegeben werden, wobei jeder Schlüssel allen Dokumenten zugeordnet wird, für die dieser Schlüssel ausgegeben wurde.

Wenn für drei Dokumente beispielsweise die Eigenschaftname auf“John Doe” festgelegt ist, wird den drei Dokumenten der Indexschlüssel“John Doe” zugeordnet.

3.2. Diereduce Funktion

Die Funktionreduce wird verwendet, um aggregierte Berechnungen unter Verwendung der Ergebnisse einer Funktionmap durchzuführen. Die Couchbase Admin-Benutzeroberfläche bietet eine einfache Möglichkeit, die integriertenreduce-Funktionen“_count”, “_sum”, und“_stats” auf Ihremap-Funktion anzuwenden.

Sie können auch Ihre eigenenreduce-Funktionen für komplexere Aggregationen schreiben. Beispiele für die Verwendung der integriertenreduce-Funktionen finden Sie später im Lernprogramm.

4. Arbeiten mit Ansichten und Abfragen

4.1. Ansichten organisieren

Ansichten werden in einem oder mehreren Designdokumenten pro Bucket organisiert. Theoretisch ist die Anzahl der Ansichten pro Designdokument unbegrenzt. Für eine optimale Leistung wurde jedoch empfohlen, jedes Designdokument auf weniger als zehn Ansichten zu beschränken.

Wenn Sie zum ersten Mal eine Ansicht in einem Designdokument erstellen, legt Couchbase diese alsdevelopment-Ansicht fest. Sie können Abfragen für die Ansicht einesdevelopmentausführen, um dessen Funktionalität zu testen. Sobald Sie mit der Ansicht zufrieden sind, würden Siepublishdas Entwurfsdokument erstellen, und die Ansicht wird zu einerproduction-Ansicht.

4.2. Abfragen erstellen

Um eine Abfrage für eine Couchbase-Ansicht zu erstellen, müssen Sie den Namen des Entwurfsdokuments und den Namen der Ansicht angeben, um einViewQuery-Objekt zu erstellen:

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

Bei der Ausführung gibt diese Abfrage alle Zeilen der Ansicht zurück. In späteren Abschnitten erfahren Sie, wie Sie die Ergebnismenge basierend auf den Schlüsselwerten einschränken können.

Um eine Abfrage für eine Entwicklungsansicht zu erstellen, können Sie beim Erstellen der Abfrage die Methodedevelopment()anwenden:

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

4.3. Abfrage ausführen

Sobald wir einViewQuery-Objekt haben, können wir die Abfrage ausführen, um einViewResult zu erhalten:

ViewResult result = bucket.query(query);

4.4. Abfrageergebnisse verarbeiten

Und jetzt, da wir einViewResulthaben, können wir über die Zeilen iterieren, um die Dokument-IDs und / oder den Inhalt zu erhalten:

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

5. Beispielanwendung

Für den Rest des Tutorials werden wir MapReduce-Ansichten und -Abfragen für eine Reihe von Schülernotendokumenten mit folgendem Format schreiben, wobei die Noten auf den Bereich von 0 bis 100 beschränkt sind:

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

Wir speichern diese Dokumente im Bucket "example-tutorial" und alle Ansichten in einem Designdokument mit dem Namen "studentGrades". Schauen wir uns den Code an, der zum Öffnen des Buckets erforderlich ist, damit wir ihn abfragen können:

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

6. Genaue Übereinstimmungsabfragen

Angenommen, Sie möchten alle Noten für einen bestimmten Kurs oder eine Reihe von Kursen finden. Schreiben wir eine Ansicht mit dem Namen "findByCourse" mit der folgendenmap-Funktion:

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

Beachten Sie, dass wir in dieser einfachen Ansicht nur das Feldcourseausgeben müssen.

6.1. Matching auf einem einzigen Schlüssel

Um alle Noten für den Verlaufskurs zu finden, wenden wir diekey-Methode auf unsere Basisabfrage an:

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

6.2. Matching auf mehreren Schlüsseln

Wenn Sie alle Noten für Mathematik- und Naturwissenschaftskurse finden möchten, können Sie die Methodekeysauf die Basisabfrage anwenden und ihr ein Array von Schlüsselwerten übergeben:

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

7. Bereichsabfragen

Um Dokumente abzufragen, die einen Wertebereich für ein oder mehrere Felder enthalten, benötigen wir eine Ansicht, die die Felder ausgibt, an denen wir interessiert sind, und wir müssen eine Unter- und / oder Obergrenze für die Abfrage angeben.

Lassen Sie uns einen Blick darauf werfen, wie Bereichsabfragen mit einem einzelnen Feld und mehreren Feldern durchgeführt werden.

7.1. Abfragen mit einem einzelnen Feld

Um alle Dokumente mit einem Bereich vongrade-Werten unabhängig vom Wert descourse-Felds zu finden, benötigen wir eine Ansicht, die nur dasgrade-Feld ausgibt. Schreiben wir die Funktionmap für die Ansicht "findByGrade":

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

Schreiben wir in dieser Ansicht eine Abfrage in Java, um alle Noten zu finden, die einer B-Note entsprechen (80 bis einschließlich 89):

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

Beachten Sie, dass der Startschlüsselwert in einer Bereichsabfrage immer als inklusive behandelt wird.

Und wenn bekannt ist, dass alle Noten ganze Zahlen sind, führt die folgende Abfrage zu denselben Ergebnissen:

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

Um alle "A" -Noten (90 und höher) zu finden, müssen wir nur die Untergrenze angeben:

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

Und um alle fehlgeschlagenen Noten (unter 60) zu finden, müssen wir nur die obere Schranke angeben:

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

7.2. Abfragen mit mehreren Feldern

Angenommen, wir möchten alle Schüler in einem bestimmten Kurs finden, deren Note in einen bestimmten Bereich fällt. Diese Abfrage erfordert eine neue Ansicht, die sowohl die Feldercourse als auchgrade ausgibt.

Bei Ansichten mit mehreren Feldern wird jeder Indexschlüssel als Array von Werten ausgegeben. Da unsere Abfrage einen festen Wert fürcourse und einen Bereich vongrade-Werten umfasst, schreiben wir die Zuordnungsfunktion, um jeden Schlüssel als Array der Form [course,gradeauszugeben ) s].

Schauen wir uns die Funktionmap für die Ansicht „findByCourseAndGrade“ an:

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

Wenn diese Ansicht in Couchbase ausgefüllt wird, werden die Indexeinträge nachcourse undgrade sortiert. Hier ist eine Teilmenge der Schlüssel in der Ansicht "findByCourseAndGrade", die in ihrer natürlichen Sortierreihenfolge angezeigt wird:

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

Da die Schlüssel in dieser Ansicht Arrays sind, würden Sie auch Arrays dieses Formats verwenden, wenn Sie die Unter- und Obergrenze einer Bereichsabfrage für diese Ansicht angeben.

Um alle Schüler zu finden, die im Mathematikkurs die Note „B“ (80 bis 89) erreicht haben, setzen Sie die Untergrenze auf:

["Math", 80]

und die Obergrenze für:

["Math", 89]

Schreiben wir die Bereichsabfrage in Java:

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

Wenn wir für alle Schüler, die eine Note von „A“ (90 und höher) in Mathematik erhalten haben, Folgendes finden möchten, schreiben wir:

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

Beachten Sie, dass wir, da wir den Kurswert auf „Math“ festlegen, eine Obergrenze mit dem höchstmöglichen Wert vongrade einfügen müssen. Andernfalls würde unsere Ergebnismenge auch alle Dokumente enthalten, derencourse-Wert lexikografisch größer als „Math“ ist.

Und um alle fehlgeschlagenen Mathematiknoten (unter 60) zu finden:

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

Ähnlich wie im vorherigen Beispiel müssen wir eine Untergrenze mit der niedrigstmöglichen Note angeben. Andernfalls würde unsere Ergebnismenge auch alle Noten enthalten, bei denen der Wert voncourselexikografisch kleiner als „Math“ ist.

Um schließlich die fünf höchsten Mathe-Noten zu finden (abgesehen von Unentschieden), können Sie Couchbase anweisen, eine absteigende Sortierung durchzuführen und die Größe der Ergebnismenge zu begrenzen:

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

Beachten Sie, dass bei einer absteigenden Sortierung die WertestartKey undendKey umgekehrt werden, da Couchbase die Sortierung anwendet, bevorlimit angewendet wird.

8. Aggregierte Abfragen

Eine wesentliche Stärke von MapReduce-Ansichten besteht darin, dass sie sehr effizient sind, um aggregierte Abfragen für große Datasets auszuführen. In unserem Datenbestand für die Noten von Schülern können wir zum Beispiel leicht die folgenden Aggregate berechnen:

  • Anzahl der Studenten in jedem Kurs

  • Summe der Kreditstunden für jeden Studenten

  • Notendurchschnitt für jeden Schüler über alle Kurse

Erstellen wir eine Ansicht und eine Abfrage für jede dieser Berechnungen mit den integriertenreduce-Funktionen.

8.1. Verwenden dercount()-Funktion

Schreiben wir zunächst diemap-Funktion für eine Ansicht, um die Anzahl der Schüler in jedem Kurs zu zählen:

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

Wir nennen diese Ansicht "countStudentsByCourse" und geben an, dass die integrierte“_count”-Funktion verwendet werden soll. Und da wir nur eine einfache Zählung durchführen, können wir immer nochnull als Wert für jeden Eintrag ausgeben.

So zählen Sie die Anzahl der Schüler in jedem Kurs:

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

Das Extrahieren von Daten aus aggregierten Abfragen unterscheidet sich von dem, was wir bisher gesehen haben. Anstatt für jede Zeile im Ergebnis ein passendes Couchbase-Dokument zu extrahieren, extrahieren wir die aggregierten Schlüssel und Ergebnisse.

Lassen Sie uns die Abfrage ausführen und die Anzahl injava.util.Map extrahieren:

ViewResult result = bucket.query(query);
Map 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. Verwenden dersum()-Funktion

Als Nächstes schreiben wir eine Ansicht, in der die Summe der versuchten Kreditstunden jedes Schülers berechnet wird. Wir nennen diese Ansicht "sumHoursByStudent" und geben an, dass die integrierte“_sum”-Funktion verwendet werden soll:

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

Beachten Sie, dass bei Anwendung der Funktion“_sum” für jeden Eintragemit der zu summierende Wert - in diesem Fall die Anzahl der Credits - sein muss.

Schreiben wir eine Abfrage, um die Gesamtzahl der Credits für jeden Schüler zu ermitteln:

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

Lassen Sie uns nun die Abfrage ausführen und die aggregierten Summen injava.util.Map extrahieren:

ViewResult result = bucket.query(query);
Map 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. Berechnung der Notendurchschnitte

Angenommen, wir möchten den Notendurchschnitt (GPA) jedes Schülers für alle Kurse anhand der herkömmlichen Notenskala berechnen, die auf den erzielten Noten und der Anzahl der Kreditstunden basiert, die der Kurs wert ist (A = 4 Punkte pro Kreditstunde, B =) 3 Punkte pro Kreditstunde, C = 2 Punkte pro Kreditstunde und D = 1 Punkt pro Kreditstunde).

Es gibt keine integriertereduce-Funktion zur Berechnung von Durchschnittswerten. Daher kombinieren wir die Ausgabe aus zwei Ansichten, um den GPA zu berechnen.

Wir haben bereits die Ansicht“sumHoursByStudent”, die die Anzahl der Kreditstunden summiert, die jeder Schüler versucht hat. Jetzt benötigen wir die Gesamtzahl der Notenpunkte, die jeder Schüler verdient hat.

Erstellen wir eine Ansicht mit dem Namen“sumGradePointsByStudent”, in der die Anzahl der für jeden Kurs erzielten Notenpunkte berechnet wird. Wir werden die integrierte“_sum”-Funktion verwenden, um die folgendemap-Funktion zu reduzieren:

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

Lassen Sie uns nun diese Ansicht abfragen und die Summen injava.util.Map extrahieren:

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

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

Kombinieren wir abschließend die zweiMaps, um den GPA für jeden Schüler zu berechnen:

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

9. Fazit

Wir haben gezeigt, wie Sie einige grundlegende MapReduce-Ansichten in Couchbase schreiben, Abfragen für die Ansichten erstellen und ausführen und die Ergebnisse extrahieren.

Der in diesem Tutorial vorgestellte Code befindet sich inGitHub project.

Sie können mehr überMapReduce views und wie manquery them in Java unter den offiziellenCouchbase developer documentation site erfährt.