Gruppieren mit $group
Teil des Abschnitts Datenbanken mit MongoDB der Node.js-Journey von Coddy. Lektion 21 von 29.
Ein Genre zu zählen ist eine Abfrage. Alle Genres auf einmal zu zählen, erfordert, dass die Datenbank die Bücher in Gruppen sortiert.
aggregate führt eine Pipeline aus: ein Array von Stufen, von denen jede ihre Ausgabe an die nächste weitergibt. Die $group-Stufe erstellt für jeden unterschiedlichen Wert von _id ein Ausgabedokument. Hier ist _id keine Dokument-ID. Es gibt an, wonach gruppiert werden soll, und jedes Ausgabedokument enthält diesen Wert in _id. Ein $ vor einem Feldnamen, '$team', bedeutet „der Wert dieses Felds in diesem Dokument“. Ohne das Zeichen ist 'team' für jedes Dokument derselbe Text, sodass sie alle in einer Gruppe landen. $sum: 1 addiert für jedes Dokument 1.
await players
.aggregate([{ $group: { _id: '$team', players: { $sum: 1 } } }])
.toArray();
// [{ _id: 'red', players: 4 }, { _id: 'blue', players: 3 }]Ein Gruppenergebnis enthält nur diese _id und die Felder, die seine Akkumulatoren berechnen; andere Felder aus den Eingabedokumenten bleiben nicht erhalten.
Aufgabe
EinfachDieser Schritt öffnet das vorbereitete Bücherregal-API-Projekt des Kurses, nicht deine Projektdateien aus Dokumente zählen (einschließlich server.js). Deine frühere Arbeit bleibt in ihrer eigenen Lektion. Lies die bereitgestellten Dateien, bevor du server.js bearbeitest. Diese Kopie enthält die CRUD-Routen für das Bücherregal, den eindeutigen ISBN-Index, die Filter für GET /books und die Zählungen für GET /stats.
Eine Genre-Seite zeigt mit nur einer Abfrage, wie viele Bücher es in jedem Genre gibt – auch in Genres, an die niemand gedacht hat.
- Füge in
server.jseine Route fürGET /stats/genreshinzu. - Führe eine Aggregation für
booksmit einer einzelnen$group-Stufe aus. Sie erstellt ein Ergebnis pro eindeutigemgenremit der Anzahl der Bücher dieses Genres. - Antworte mit Status 200 und dem Array, das die Aggregation zurückgibt. Jeder Eintrag enthält das Genre in
_idund die Anzahl der Bücher incount. Innerhalb von$groupist_idkeine ObjectId: Es ist der Wert, nach dem die Bücher gruppiert wurden. Die Einträge können in beliebiger Reihenfolge erscheinen. Mit den Büchern aus dem Starterprojekt lautet die Antwort:Probiere es aus: Sende[ { "_id": "scifi", "count": 2 }, { "_id": "classic", "count": 1 } ]GET /stats/genres: Du erhältst die beiden obigen Einträge. SendePOST /booksmit{ "title": "Leaves of Grass", "author": "Walt Whitman", "year": 1855, "isbn": "9780140421996", "genre": "poetry" }: Du erhältst den Status 201. Sende erneutGET /stats/genres: Ein dritter Eintrag erscheint,{ "_id": "poetry", "count": 1 }.
Probier es selbst
import express from 'express';
import { MongoClient, ObjectId } from 'mongodb';
const client = new MongoClient('mongodb://localhost:27017');
const db = client.db('shelf');
const books = db.collection('books');
const ratings = db.collection('ratings');
await books.createIndex({ isbn: 1 }, { unique: true });
await books.insertMany([
{ title: 'Dune', author: 'Frank Herbert', year: 1965, isbn: '9780441013593', genre: 'scifi' },
{ title: 'Kindred', author: 'Octavia E. Butler', year: 1979, isbn: '9780807083697', genre: 'scifi' },
{ title: 'Emma', author: 'Jane Austen', year: 1815, isbn: '9780141439587', genre: 'classic' },
]);
const app = express();
app.use(express.json());
// Bei jedem Fehler wird { error: message } zurückgegeben.
function sendError(res, status, message) {
res.status(status).json({ error: message });
}
// Die :id als ObjectId oder null, nachdem bei einer ungültigen id mit 400 geantwortet wurde.
function parseId(req, res) {
if (ObjectId.isValid(req.params.id)) {
return new ObjectId(req.params.id);
}
sendError(res, 400, 'Not a valid id');
return null;
}
app.get('/', (req, res) => {
res.send('Bookshelf API');
});
app.get('/books', async (req, res) => {
const filter = {};
if (req.query.author) {
filter.author = req.query.author;
}
if (req.query.from || req.query.to) {
filter.year = {};
if (req.query.from) {
filter.year.$gte = Number(req.query.from);
}
if (req.query.to) {
filter.year.$lte = Number(req.query.to);
}
}
const list = await books.find(filter).toArray();
res.json(list);
});
app.post('/books', async (req, res) => {
const book = req.body;
try {
await books.insertOne(book);
} catch (err) {
if (err.code === 11000) {
return sendError(res, 409, 'That ISBN is already on the shelf');
}
throw err;
}
res.status(201).json(book);
});
app.get('/books/newest', async (req, res) => {
const n = Number(req.query.n) || 3;
const list = await books
.find({}, { projection: { title: 1, year: 1, _id: 0 } })
.sort({ year: -1 })
.limit(n)
.toArray();
res.json(list);
});
app.get('/books/:id', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const book = await books.findOne({ _id: id });
if (!book) {
return sendError(res, 404, 'No such book');
}
res.json(book);
});
app.patch('/books/:id', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const result = await books.updateOne({ _id: id }, { $set: req.body });
if (result.matchedCount === 0) {
return sendError(res, 404, 'No such book');
}
res.json(await books.findOne({ _id: id }));
});
app.post('/books/:id/copies', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const result = await books.updateOne({ _id: id }, { $inc: { copies: 1 } });
if (result.matchedCount === 0) {
return sendError(res, 404, 'No such book');
}
res.json(await books.findOne({ _id: id }));
});
app.post('/books/:id/tags', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const result = await books.updateOne({ _id: id }, { $push: { tags: req.body.tag } });
if (result.matchedCount === 0) {
return sendError(res, 404, 'No such book');
}
res.json(await books.findOne({ _id: id }));
});
app.delete('/books/:id', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const result = await books.deleteOne({ _id: id });
if (result.deletedCount === 0) {
return sendError(res, 404, 'No such book');
}
res.status(204).end();
});
app.put('/books/:id/ratings', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const { user, stars } = req.body;
await ratings.updateOne({ bookId: id, user }, { $set: { stars } }, { upsert: true });
res.json({ user, stars });
});
app.get('/books/:id/ratings', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
res.json(await ratings.find({ bookId: id }).toArray());
});
app.get('/stats', async (req, res) => {
const filter = req.query.genre ? { genre: req.query.genre } : {};
res.json({
books: await books.countDocuments(filter),
ratings: await ratings.countDocuments(),
});
});
app.listen(3000, () => {
console.log('Bookshelf is running on port 3000');
});
Alle Lektionen in Datenbanken mit MongoDB
1Dokumente
Ein Buch als DokumentSammlungen und DatenbankenVerbindung zu MongoDB herstellenGespeicherte Dokumente lesenDokumente einfügenEin Dokument findenÜber ObjectId suchenZahlen vergleichenAbfragefilterZurückgegebene Felder auswählenAbfrageergebnisse sortierenDie ersten Ergebnisse behaltenProjektion, Sortierung und LimitÜbe selbstständig: Express-Playground