חלוקה לעמודים במסד הנתונים
חלק מהיחידה מסדי נתונים עם MongoDB במסלול ה-Node.js של Coddy. שיעור 27 מתוך 29.
מדף של עשרת אלפים ספרים לא יכול להישלח בתגובה אחת. הלקוח מבקש עמוד אחד בכל פעם, ובסיס הנתונים צריך לשלוח רק את אותו עמוד.
skip(n) מדלגת על n ההתאמות הראשונות, ו-limit(n) משאירה את n הבאות. עמוד 1 מתחיל מההתחלה, ולכן עמוד p מדלג על (p - 1) * size. שלחו את הסך הכול יחד עם העמוד, כשהספירה נעשית באמצעות אותו מסנן, כדי שהלקוח יידע כמה עמודים יש.
// עמוד 3 של יומן השגיאות, 20 רשומות בכל עמוד
const entries = await log
.find({ level: 'error' })
.skip((3 - 1) * 20)
.limit(20)
.toArray();
const total = await log.countDocuments({ level: 'error' });אתגר
קלמדף עם עשרת אלפים ספרים לא יכול להישלח בתגובה אחת. GET /books שולח עכשיו עמוד אחד בכל פעם. הוא גם מציין כמה ספרים תואמים בסך הכול, כך שלקוח יכול להציג "עמוד 1 מתוך 5".
- ב־
server.js, שנה אתGET /books. השאר את המסנניםauthor,fromו־to. - קרא את
?page=(ברירת מחדל 1) ואת?limit=(ברירת מחדל 10). אם אחד מהם חסר או אינו מספר, השתמש בברירת המחדל שלו. עמוד 1 מתחיל בספר הראשון, לכן עמודpמדלג על(p - 1) * limitספרים ואז מחזיר אתlimitהספרים הבאים. תן למסד הנתונים לבצע את הדילוג ואת החיתוך. - ספור את
totalבמסד הנתונים, באמצעות אותו מסנן. הספירה כוללת כל ספר תואם, ולא רק את הספרים שבעמוד הזה. - החזר תשובה עם קוד סטטוס 200 ואובייקט, לא מערך.
GET /books?limit=2מחזיר:נסה זאת: שלח{ "items": [ { "_id": "6720a1f4c2b9e81d3a5f0c11", "title": "Dune", "author": "Frank Herbert", "year": 1965, "isbn": "9780441013593", "genre": "scifi" }, { "_id": "6720a1f4c2b9e81d3a5f0c12", "title": "Kindred", "author": "Octavia E. Butler", "year": 1979, "isbn": "9780807083697", "genre": "scifi" } ], "total": 3 }GET /books?limit=2: תקבל את Dune ואת Kindred, עם"total": 3. שלחGET /books?limit=2&page=2: תקבל רק את Emma, עדיין עם"total": 3. שלחGET /books?limit=2&page=3: תקבל"items": []ואת"total": 3. שלחGET /books?author=Jane%20Austen&limit=1: תקבל את Emma, עם"total": 1. שלחGET /books: תקבל את כל שלושת הספרים בתוךitems.
נסו בעצמכם
import express from 'express';
import { ObjectId } from 'mongodb';
import { books, ratings } from './db.js';
const app = express();
app.use(express.json());
// כל שגיאה מחזירה { error: message }.
function sendError(res, status, message) {
res.status(status).json({ error: message });
}
// מחזירה את :id בתור ObjectId, או null לאחר החזרת 400 עבור id לא תקין.
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.get('/stats/genres', async (req, res) => {
const genres = await books
.aggregate([{ $group: { _id: '$genre', count: { $sum: 1 } } }])
.toArray();
res.json(genres);
});
app.get('/stats/top', async (req, res) => {
const top = await ratings
.aggregate([
{ $group: { _id: '$bookId', avg: { $avg: '$stars' }, count: { $sum: 1 } } },
{ $match: { count: { $gte: 2 } } },
{ $sort: { avg: -1 } },
{ $limit: 3 },
])
.toArray();
res.json(top);
});
app.get('/books/:id/full', async (req, res) => {
const id = parseId(req, res);
if (!id) return;
const [book] = await books
.aggregate([
{ $match: { _id: id } },
{
$lookup: {
from: 'authors',
localField: 'author',
foreignField: 'name',
as: 'authorInfo',
},
},
])
.toArray();
if (!book) {
return sendError(res, 404, 'No such book');
}
res.json(book);
});
app.listen(3000, () => {
console.log('Bookshelf is running on port 3000');
});
כל השיעורים ביחידה מסדי נתונים עם MongoDB
תרגלו בעצמכם: Express אונליין