Mengelompokkan dengan $group
Termasuk bagian Database dengan MongoDB dalam perjalanan Node.js di Coddy. Pelajaran 21 dari 29.
Menghitung satu genre memerlukan satu query. Untuk menghitung semua genre sekaligus, database perlu mengelompokkan buku.
aggregate menjalankan pipeline: sebuah array berisi tahap, yang masing-masing meneruskan output-nya ke tahap berikutnya. Tahap $group membuat satu dokumen output untuk setiap nilai berbeda dari _id. Di sini, _id bukan ID dokumen. Nilai ini menentukan pengelompokan, dan setiap dokumen output menyimpan nilai tersebut di _id. Tanda $ sebelum nama field, seperti '$team', berarti "nilai field tersebut pada dokumen ini". Tanpa tanda itu, 'team' menjadi teks yang sama untuk setiap dokumen, sehingga semuanya masuk ke dalam satu kelompok. $sum: 1 menambahkan 1 untuk setiap dokumen.
await players
.aggregate([{ $group: { _id: '$team', players: { $sum: 1 } } }])
.toArray();
// [{ _id: 'red', players: 4 }, { _id: 'blue', players: 3 }]Hasil pengelompokan hanya berisi _id dan field yang dihitung oleh akumulatornya. Field lain dari dokumen input tidak disertakan.
Tantangan
MudahLangkah ini membuka proyek API rak buku yang sudah disiapkan untuk kursus, bukan file proyekmu dari Counting Documents (termasuk server.js). Pekerjaanmu sebelumnya tetap berada di pelajarannya sendiri. Baca file yang disediakan sebelum mengedit server.js. Salinan ini berisi route CRUD rak buku, indeks ISBN unik, filter GET /books, dan jumlah GET /stats.
Halaman genre menunjukkan jumlah buku untuk setiap genre, semuanya dari satu query, termasuk genre yang tidak direncanakan siapa pun.
- Di
server.js, tambahkan routeGET /stats/genres. - Jalankan agregasi pada
booksdengan satu tahap$group. Tahap ini menghasilkan satu hasil untuk setiapgenreyang berbeda, beserta jumlah buku yang memilikinya. - Berikan respons dengan status 200 dan array yang dikembalikan agregasi. Setiap entri menyimpan genre di
_iddan jumlah buku dicount. Di dalam$group,_idbukan ObjectId: nilainya adalah nilai yang digunakan untuk mengelompokkan buku. Urutan entri boleh apa saja. Dengan buku-buku awal, responsnya adalah:Coba: Kirim[ { "_id": "scifi", "count": 2 }, { "_id": "classic", "count": 1 } ]GET /stats/genres: kamu mendapatkan dua entri di atas. KirimPOST /booksdengan{ "title": "Leaves of Grass", "author": "Walt Whitman", "year": 1855, "isbn": "9780140421996", "genre": "poetry" }: kamu mendapatkan 201. KirimGET /stats/genreslagi: entri ketiga muncul,{ "_id": "poetry", "count": 1 }.
Coba sendiri
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());
// Setiap error menghasilkan respons { error: message }.
function sendError(res, status, message) {
res.status(status).json({ error: message });
}
// Ubah :id menjadi ObjectId, atau kembalikan null setelah mengirim respons 400 jika id tidak valid.
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');
});
Semua pelajaran di Database dengan MongoDB
1Dokumen
Buku sebagai DokumenKoleksi dan DatabaseMenghubungkan ke MongoDBMembaca Dokumen TersimpanMenyisipkan DokumenMenemukan Satu DokumenMenemukan Dokumen berdasarkan ObjectIdMembandingkan AngkaFilter QueryMemilih Field yang DikembalikanMengurutkan Hasil QueryMempertahankan Hasil PertamaProyeksi, Pengurutan, dan BatasLatihan sendiri: Playground Express