Jede verfügbare Produktvariante finden

MongoDBBeginner
Jetzt üben

Einführung

RIDGE & REED verkauft Outdoorbekleidung. Eine Kundin oder ein Kunde sucht einen grünen Artikel in Größe M, der vorrätig ist und höchstens 5000 Cent kostet. Ein Produkt kann eine grüne Variante und eine vorrätige Variante in Größe M haben, ohne dass eine grüne Variante in Größe M verfügbar ist. Selbst nach der Auswahl des richtigen Produkts würde es die Kundschaft weiterhin irreführen, dessen gesamtes Variantenarray oder dessen günstigste nicht verfügbare Option anzuzeigen.

Du speicherst einen wiederverwendbaren Produktselektor und erstellst anschließend eine MongoDB-Aggregationspipeline, die alle geeigneten SKUs zurückgibt und den niedrigsten Preis aus genau diesen Varianten berechnet. Du benötigst grundlegende Kenntnisse der Navigation im Terminal, von MongoDB-Verbindungen, Objekten/Arrays sowie einfachen find/update-Operationen. JavaScript-Funktionsvorlagen, Aggregationsausdrücke und -stufen werden vor ihrer Verwendung erklärt. Verwende durchgehend Terminal; MongoDB, synthetische Bestände und schreibgeschützte Clients sind vorbereitet.

Ändere nur selector.js und finder.js. Bewahre alle ursprünglichen Produkte, Varianten-IDs, Attribute, Bestände und Preise sowie die bereitgestellten Werkzeuge. Verfügbarkeit ist eine vorbereitete Bestandszahl, keine Reservierungs- oder Kaufabschlussgarantie. Jede tatsächliche Variante hat SKU/Farbe/Größe als Text sowie nicht negative ganzzahlige Bestände und Preise in Cent. Die Varianten eines Produkts können ein Array, null oder nicht vorhanden sein; andere fehlerhafte Eingabetypen liegen außerhalb dieser Übung.

Alle Bedingungen auf dieselbe Variante anwenden

In diesem Schritt vergleichst du eine Kundenanfrage mit einem tatsächlichen Produkt und speicherst einen wiederverwendbaren Selektor, der einen irreführenden Treffer über verschiedene Varianten hinweg ablehnt. Beginne in Terminal. cd wechselt das Verzeichnis; verwende diesen absoluten Pfad erneut, falls du in ein falsches Verzeichnis wechselst:

cd /home/labex/project/variant-desk

./catalog ist ein bereitgestelltes Leseprogramm in diesem Verzeichnis; ./ bedeutet das aktuelle Verzeichnis. Übergib ihm eine Produkt-ID, um das tatsächlich gespeicherte Dokument zu lesen:

./catalog TRAIL-1

Trail Jacket hat diese drei unterschiedlichen Varianten:

SKU Farbe Größe Bestand Preis in Cent
T-GM green M 0 1800
T-BM blue M 5 2200
T-GL green L 4 2100

Die grüne Variante in Größe M ist ausverkauft. Die blaue Variante in Größe M und die grüne in Größe L sind verfügbar, aber keine erfüllt die vollständige Anfrage. Eine SKU identifiziert eine Variante; eine Produkt-ID identifiziert deren übergeordnetes Produkt. Bewahre diese Beziehungen.

./db verbindet die MongoDB-Shell mit dem schreibgeschützten Katalog dieses Kurses. --eval führt das JavaScript in Anführungszeichen aus und beendet sich. Einfache Shell-Anführungszeichen schützen Leerzeichen und MongoDB-Operatoren mit Dollarpräfix vor der Shell-Expansion; doppelte Anführungszeichen darin gehören zu JavaScript-Zeichenfolgen. Punktpfade wie "variants.color" erreichen verschachtelte Felder. $gt: 0 bedeutet größer als null; $lte: 5000 bedeutet kleiner oder gleich 5000, einschließlich der Budgetgrenze. Zahlenwerte sind Zahlen, keine Währungszeichenfolgen in Anführungszeichen.

Diese gewöhnliche Punktpfadabfrage ist absichtlich zu weit gefasst. Das zweite find-Argument projiziert nur die Produkt-ID: 1 schließt ein Feld ein, und _id: 0 lässt MongoDBs interne ID weg. sort mit 1 sortiert aufsteigend; toArray() sammelt die zurückgegebenen Dokumente in einem lesbaren Array:

./db --eval 'db.products.find({"variants.color":"green","variants.size":"M","variants.stock":{$gt:0},"variants.price_cents":{$lte:5000}},{_id:0,product_id:1}).sort({product_id:1}).toArray()'

Erwartet werden CAMP-3, RIDGE-2 und TRAIL-1. TRAIL-1 ist der falsche Treffer: Unterschiedliche Arrayelemente liefern unterschiedliche Bedingungen. Die Abfrage wurde erfolgreich ausgeführt, aber ihre fachliche Antwort ist falsch.

$elemMatch legt alle Kriterien auf ein Arrayelement. Vielleicht kennst du diesen Operator bereits aus den MongoDB-Arraygrundlagen; hier ist er der erste Baustein eines vollständigen Ergebnisses für die Kundschaft:

./db --eval 'db.products.find({variants:{$elemMatch:{color:"green",size:"M",stock:{$gt:0},price_cents:{$lte:5000}}}},{_id:0,product_id:1}).sort({product_id:1}).toArray()'

Nun bleiben nur CAMP-3 und RIDGE-2 übrig. Ein passendes Produkt hat mindestens eine geeignete Variante. Das bedeutet noch nicht, dass jede Variante dieses Produkts geeignet ist.

Speichere einen Selektor, der die Anfrage als Argument entgegennimmt, statt green/M/5000 in der Abfrage festzuschreiben. Eine JavaScript-Funktion ist wiederverwendbarer Code: function buildSelector(request) benennt sie und empfängt ein Objekt namens request; request.color liest die Farbeigenschaft dieses Objekts. return liefert das Abfrageobjekt an den vorbereiteten Client. Geschweifte Klammern enthalten Objekte oder den Funktionsrumpf, Doppelpunkte verbinden Objektschlüssel mit Werten, und Kommas trennen Eigenschaften. Die erforderliche Schnittstelle der Funktion ist buildSelector(request), die ein Abfrageobjekt zurückgibt. Sie ist weder ein Shell-Befehl noch ein Modulexport.

In der Shell ersetzt cat > die Zieldatei. <<'JS' beginnt ein wörtliches Here-Dokument, das Dollarzeichen und Anführungszeichen bewahrt. Füge das abschließende JS allein in einer Zeile ein; gib den gesamten Block an der Shell-Eingabeaufforderung ein:

cat > selector.js <<'JS'
function buildSelector(request) {
  return {
    variants: { $elemMatch: {
      color: request.color,
      size: request.size,
      stock: { $gt: 0 },
      price_cents: { $lte: request.max_cents }
    } }
  };
}
JS

./select liest diese gespeicherte Funktion und führt ihren zurückgegebenen Selektor unverändert mit MongoDB find aus. Seine drei Argumente sind Farbe, Größe und Höchstpreis in Cent. Der Client wandelt das Budget in eine Zahl um; Zeichenfolgen wie green und M bleiben exakte Bezeichnungen, bei denen Groß- und Kleinschreibung zählt:

./select green M 5000

Die tatsächlich passenden Dokumente sind CAMP-3 und RIDGE-2. Ihre gesamten ursprünglichen Arrays erscheinen weiterhin, einschließlich nicht passender Varianten. Das ist bei find normal: Die Auswahl wählt Dokumente aus, nicht die anzuzeigenden Elemente. Bewahre den Selektor als nützliches Ergebnis und löse im nächsten Schritt das Anzeigeproblem.

Fehlt eine Datei, verwende ls, um dieses Verzeichnis aufzulisten, und prüfe den Dateinamen und das absolute Arbeitsverzeichnis. Ersetze bei einem JavaScript-Syntaxfehler den vollständigen gespeicherten Block durch seine korrigierte Fassung und führe den Client erneut aus. Wartet die Shell weiterhin, gib die fehlende abschließende Zeile JS ein. Ändere keine Quellbestände, lösche keine Varianten und setze die Datenbank nicht zurück, um eine Abfrage korrekt erscheinen zu lassen.

Jede geeignete SKU zurückgeben

In diesem Schritt überführst du die Produktauswahl in ein vollständiges Variantenergebnis, ohne nur den ersten Treffer zurückzugeben oder das gesamte Array auszugeben. Beginne im selben Terminal unter /home/labex/project/variant-desk.

Lies die beiden echten Treffer:

./catalog RIDGE-2
./catalog CAMP-3

Für green/M mit höchstens 5000 Cent hat Ridge Jacket zwei geeignete SKUs: R-GM1 mit 4500 und R-GM2 mit 5000. R-GM3 ist zu teuer; andere Größen/Farben und die Variante mit Bestand 0 sind nicht geeignet. Camp Vest hat C-GM1 und C-GM2, beide mit 4000. Gleiche Preise machen unterschiedliche SKUs nicht zu Duplikaten.

Eine positionale Projektion, "variants.$": 1, gibt nur das erste passende Arrayelement zurück. Sieh dir an, weshalb sie die vollständige Menge dieser Anfrage nicht liefern kann:

./db --eval 'db.products.find({variants:{$elemMatch:{color:"green",size:"M",stock:{$gt:0},price_cents:{$lte:5000}}}},{_id:0,product_id:1,"variants.$":1}).sort({product_id:1}).toArray()'

Jedes Produkt hat in dieser Ausgabe nur eine Variante. Die zweite zulässige SKU fehlt, obwohl sie verfügbar ist. Das gesamte ursprüngliche Array zurückzugeben, würde in die andere Richtung scheitern, weil unerwünschte Varianten enthalten wären.

Eine Aggregationspipeline ist ein Array aus Stufen, die der Reihe nach verarbeitet werden. Jede Stufe empfängt die Dokumente der vorherigen Stufe. $project wählt Ausgabefelder aus und kann ein neues Feld berechnen. $filter berechnet ein Array mit jedem Element, dessen Bedingung wahr ist; hier wird keine Begrenzung angewendet.

Innerhalb von $filter ist input das Quellarray, und as: "v" benennt die aktuelle Variante. "$variants" verweist auf ein Dokumentfeld; "$$v.color" verweist auf die Farbe des aktuellen Arrayelements. Diese Dollarzeichen sind MongoDB-Ausdrücke innerhalb von JavaScript-Zeichenfolgen. request.color ist ein vom Client gelieferter JavaScript-Anfragewert.

Aggregationsvergleiche verwenden ein Operandenarray: { $eq: [left, right] } prüft Gleichheit, $gt prüft größer als, und $lte prüft kleiner oder gleich. $and verlangt, dass jeder Vergleich für dasselbe v wahr ist. Dies unterscheidet sich von der Abfrageobjekt-Schreibweise in $elemMatch, obwohl die fachlichen Bedingungen identisch sind.

$ifNull: ["$variants", []] ersetzt ein Quellfeld mit dem Wert null oder ein fehlendes Quellfeld durch ein leeres Array. [] ist ein leeres JavaScript-Array. Ein Wert, der kein Array ist, etwa eine Zeichenfolge, liegt außerhalb der vorgegebenen Eingabeanforderungen; diese Pipeline beansprucht nicht, beliebige fehlerhafte Kataloge zu bereinigen.

Nach dem Filtern entfernt $match Produkte ohne geeignete Varianten. "variants.0" verweist auf Index null, das erste Arrayelement; $exists: true verlangt, dass es existiert. Dies ist ein Abfrageprädikat in einer Match-Stufe, kein Ausdruck innerhalb der Filterbedingung. Das abschließende $sort macht die tatsächliche Ausgabe bequem lesbar.

Speichere die vollständige Vorlage in finder.js. Ihre erforderliche Schnittstelle ist buildPipeline(request), die ein Array von Stufen zurückgibt. Der Client führt diese zurückgegebene Pipeline unverändert aus; er filtert ihre Antwort nicht nachträglich:

cat > finder.js <<'JS'
function buildPipeline(request) {
  return [
    { $project: {
        _id: 0, product_id: 1, title: 1,
        variants: { $filter: {
          input: { $ifNull: ["$variants", []] },
          as: "v",
          cond: { $and: [
            { $eq: ["$$v.color", request.color] },
            { $eq: ["$$v.size", request.size] },
            { $gt: ["$$v.stock", 0] },
            { $lte: ["$$v.price_cents", request.max_cents] }
          ] }
        } }
    } },
    { $match: { "variants.0": { $exists: true } } },
    { $sort: { product_id: 1 } }
  ];
}
JS
./finder green M 5000

Erwartet werden genau zwei Produkte. CAMP-3 enthält C-GM1 und C-GM2; RIDGE-2 enthält R-GM1 und R-GM2. Jede zurückgegebene Variante bewahrt ihre exakte SKU, Farbe, Größe, ihren Bestand und Preis in Cent. Das Produkt bewahrt seine exakten Werte für product_id und title. Es gibt noch kein Mindestpreisfeld; das folgt im nächsten Schritt.

Die Pipeline gibt nun alle vier geeigneten SKUs zurück, einschließlich der beiden gleich teuren Camp-Vest-Varianten und der Ridge-SKU genau an der Budgetgrenze. TRAIL-1, leere Arrays und null/fehlende Arrays erzeugen keine Ergebnisprodukte. Kein Quelldokument wurde gelöscht oder bearbeitet, um dieses Ergebnis zu erzeugen.

Meldet der Client, dass buildPipeline ein Pipelinearray zurückgeben muss, prüfe, ob die gespeicherte Datei die gesamte Funktion und return [ ... ]; enthält. Meldet er einen Syntaxfehler, ersetze den vollständigen Block und versuche es erneut. Bewahre das Abfrage-JavaScript in der gespeicherten Datei; füge keine Shell-Befehle, keinen interaktiven use-Befehl und keine Modulexporte hinzu.

Den Preis der Treffermenge bestimmen und die Suche wiederverwenden

In diesem Schritt ergänzt du den niedrigsten geeigneten Preis und verwendest dieselbe gespeicherte Abfrage für unterschiedliche Kundenanfragen, einschließlich eines ehrlichen leeren Ergebnisses. Beginne in Terminal im Verzeichnis /home/labex/project/variant-desk.

Die günstigste ursprüngliche Variante von Ridge Jacket kostet 900 Cent, hat aber Bestand 0. Eine andere günstige Variante hat die falsche Farbe. Eine davon als Einstiegspreis für green/M zu bewerben, würde die Kundschaft irreführen, selbst wenn das Produkt selbst korrekt ausgewählt wurde.

$set ergänzt oder ersetzt ein Ausgabefeld in einer Aggregationsstufe; das gespeicherte Produkt wird nicht aktualisiert. Hier ergänzt es min_price_cents. Der Ausdruck $min mit einem Arrayoperanden gibt dessen kleinsten Zahlenwert zurück. "$variants.price_cents" erreicht die Preise im Variantenarray des aktuellen Dokuments. Platziere diese Stufe nach dem Filtern: Sie muss das von $project erzeugte passende Array lesen, nicht das ursprüngliche Array. Eine spätere Stufe macht dieses geänderte Feld außerdem eindeutig verfügbar.

Ersetze finder.js durch die vollständige Pipeline unten. Die einzige neue Stufe berechnet das Minimum, nachdem die leeren Arrays ausgeschlossen wurden:

cat > finder.js <<'JS'
function buildPipeline(request) {
  return [
    { $project: {
        _id: 0, product_id: 1, title: 1,
        variants: { $filter: {
          input: { $ifNull: ["$variants", []] },
          as: "v",
          cond: { $and: [
            { $eq: ["$$v.color", request.color] },
            { $eq: ["$$v.size", request.size] },
            { $gt: ["$$v.stock", 0] },
            { $lte: ["$$v.price_cents", request.max_cents] }
          ] }
        } }
    } },
    { $match: { "variants.0": { $exists: true } } },
    { $set: { min_price_cents: { $min: "$variants.price_cents" } } },
    { $sort: { product_id: 1 } }
  ];
}
JS
./finder green M 5000

Das Ergebnis muss alle vier geeigneten SKUs beibehalten und das Minimum 4000 für CAMP-3 sowie 4500 für RIDGE-2 ergänzen. min_price_cents wird aus der jeweils zurückgegebenen Variantenmenge abgeleitet. Entferne keine gleichen Preise als Duplikate, verwende kein produktweites Minimum und schreibe diese beiden Zahlen nicht von Hand fest.

Senke nun das Budget mit derselben Funktion. Die Werte der Anfrage ändern sich, die gespeicherte Abfrage nicht:

./finder green M 4499

Nur CAMP-3 bleibt übrig, weiterhin mit beiden SKUs zu 4000 Cent und dem Minimum 4000. Ridge Jacket hat keine geeignete green/M-Variante unter dieser neuen Obergrenze.

Ändere Farbe und Obergrenze:

./finder blue M 2500

RIDGE-2 enthält nun nur R-BM mit 1000; TRAIL-1 enthält nur T-BM mit 2200. Ein für eine Anfrage korrekt ausgeschlossenes Produkt kann für eine andere eingeschlossen werden. Verfügbarkeit gehört zu einer bestimmten Varianten-/Anfragebeziehung, nicht zu einer dauerhaften Produktmarkierung.

Probiere zum Schluss ein Budget ohne Treffer:

./finder green M 500

Das tatsächliche Ergebnis ist []. Der Client ersetzt es weder durch ein Demoergebnis noch zeigt er nicht verfügbare Artikel an, um den Bildschirm zu füllen. Eine fehlende Abfrage oder ein Datenbankfehler wird als Fehler gemeldet und nicht als dieses erfolgreiche leere Array getarnt.

Dein endgültiges Pipelineergebnis enthält genau product_id, title, variants und min_price_cents. Gib jedes passende Produkt einmal und jede geeignete SKU einmal mit allen fünf ursprünglichen Variantenfeldern zurück. Die Produkt-/Variantenreihenfolge ist frei; Identitäten und Werte sind die fachlichen Anforderungen. Die Abfrage muss auch einen weiteren Katalog mit demselben Schema und anderen IDs, Preisen und Beständen verarbeiten. Die vorbereiteten Clients lesen echte MongoDB-Ergebnisse und korrigieren weder Auswahl noch Arrays oder Preise.

Bewahre beide gespeicherten JavaScript-Dateien und alle ursprünglichen Produkte/Werkzeuge. Du hast eine wiederverwendbare Katalogabfrage erstellt, kein Reservierungssystem: Gleichzeitige Bestandsänderungen oder ein späterer Kaufabschluss könnten die Verfügbarkeit weiterhin beeinflussen. Die folgende Challenge hat ihr eigenes unabhängiges Mietinventar und verwendet dieselben Abfragemechanismen mit anderen Attributnamen und Anfragewerten.

Zusammenfassung

Du hast Treffer abgelehnt, deren Bedingungen auf verschiedene Varianten verteilt waren, jede geeignete SKU statt nur des ersten Treffers beibehalten und Anzeigepreise aus gefilterten Arrays berechnet. Deine gespeicherten Funktionen verarbeiten geänderte Anfragen, gleiche Preise und leere Ergebnisse und bewahren dabei den ursprünglichen Katalog.