MongoDB: أساسيات الاستعلام عن المستندات

آخر تحديث: 2026-08-26

تعد الاستعلامات العمليات الأساسية لاسترداد البيانات من MongoDB — وإتقان طريقة find هو الخطوة الأولى في التفاعل مع قاعدة البيانات.

تقدم هذه الدورة استكشافًا متعمقًا لبناء جمل الاستعلام find وfindOne، وإسقاط الحقول، وتقسيم الصفحات والفرز، بالإضافة إلى تنسيق نتائج الاستعلام ومعالجتها.

1. ما ستتعلمه



2. قصة حقيقية لمهندس متكامل

(1) المشكلة: تؤدي الاستعلامات التي تُرجع جميع الحقول إلى زيادة كبيرة في الحمل على الشبكة

تشارلي هو مهندس متكامل في مجال التجارة الإلكترونية، ويعمل حالياً على تحسين أداء واجهة برمجة التطبيقات (API) الخاصة بقائمة المنتجات:

"تُرجع واجهة برمجة التطبيقات (API) الخاصة بقائمة منتجاتي 100 منتج، يبلغ حجم كل منها 5 كيلوبايت، لكن الواجهة الأمامية لا تعرض سوى ثلاثة حقول: العنوان، والسعر، والصورة. وهي تُرجع 50 كيلوبايت من البيانات غير الضرورية، وتستغرق استجابة واجهة برمجة التطبيقات (API) 800 مللي ثانية، مما يؤدي إلى إهدار النطاق الترددي ووقت التحليل."

كود الاستعلام الأصلي:

JAVASCRIPT
// ❌ Counterexample:Return all fields
app.get('/api/products', async (req, res) => {
  const products = await Product.find();  // Return all fields
  res.json(products);
});
// Each product 5KB,100 items = 500KB
// Slow Internet Connection + Slow front-end parsing

(2) حل مسألة الإسقاط

JAVASCRIPT
// ✅ Correct Example:Return only the necessary fields
app.get('/api/products', async (req, res) => {
  const products = await Product.find(
    { isActive: true },
    {
      projection: {
        sku: 1,
        title: 1,
        price: 1,
        thumbnail: 1,
        _id: 0   // Exclusion _id
      }
    }
  )
    .sort({ createdAt: -1 })
    .limit(20)
    .lean();   // Skip mongoose hydrate,Performance ↑3-5x

  res.json(products);
});
// Each product 200 Byte,20 items = 4KB(Performance ↑100x)

(3) الإيرادات

البعد غير المتوقع المتوقع
حجم الرد 500 كيلوبايت 4 كيلوبايت
زمن استجابة واجهة برمجة التطبيقات 800 مللي ثانية 50 مللي ثانية
وقت التحليل في الواجهة الأمامية 200 مللي ثانية 5 مللي ثانية
عرض النطاق الترددي للشبكة عالي منخفض (100x)


3. find و findOne

شرح المفهوم: find وfindOne هما طريقتا الاستعلام الرئيسيتان في MongoDB. تُرجع find مؤشرًا للمستندات المطابقة وهي مناسبة لاستعلامات القوائم؛ بينما تُرجع findOne مستندًا واحدًا وهي مناسبة لاستعلامات التفاصيل. ورغم تشابه صيغتهما، فإن أنواع القيم التي تُرجعانها تختلف؛ وفهم هذا الاختلاف أمر بالغ الأهمية للتعامل بشكل صحيح مع نتائج الاستعلام.

كيفية العمل: لا تُرجع find جميع البيانات على الفور؛ بل تقوم بدلاً من ذلك بإنشاء كائن Cursor. ويستخدم كائن Cursor استراتيجية التحميل المؤجل — حيث يتم جلب البيانات من الخادم على دفعات (101 سجل أو 1 ميغابايت لكل دفعة بشكل افتراضي) فقط عند إجراء تكرار عليها أو استدعاء toArray(). يضمن هذا التصميم ألا تستنفد find الذاكرة، حتى مع وجود مجموعات بيانات تضم ملايين السجلات. findOne تعادل find().limit(1)، لكنها تُرجع المستندات مباشرةً بدلاً من كائن Cursor.

100%
sequenceDiagram
    participant App as Applications
    participant Mongo as MongoDB Server-side

    App->>Mongo: find({ category: "Electronics" })
    Mongo-->>App: Cursor Object(No data retrieved)
    App->>Mongo: cursor.next() / toArray()
    Mongo-->>App: The first batch 101 Documents
    App->>Mongo: Continue iterating
    Mongo-->>App: Subsequent batches(Maximum per batch 16MB)
البعد find findOne
نوع القيمة المرجعة مؤشر مستند أو null
عدد المباريات جميع المباريات المباراة الأولى
استخدام الذاكرة التحميل التدفقي (التحميل المؤجل) لمرة واحدة
الأداء سريع أسرع قليلاً (لا يُنشئ مؤشراً)
حالات الاستخدام استعلام القائمة استعلام التفاصيل

(1) استخدام find للبحث في مستندات متعددة

JAVASCRIPT
// === find Basic Syntax ===
db.products.find();
// Back to All Documents(cursor)

// === find Specified Conditions ===
db.products.find({ category: "Electronics" });
// Back to All Electronics

// === find Return an array ===
db.products.find({ category: "Electronics" }).toArray();
// Back Array<Document>

// === find Iterate(Cursor)===
db.products.find({ category: "Electronics" }).forEach(printjson);

(2) findOne: الاستعلام عن مستند واحد

شرح المفهوم: findOne هي طريقة ملائمة للاستعلام عن مستند واحد؛ ومن الناحية الداخلية، فهي تعادل find().limit(1)، لكنها تُرجع كائن مستند مباشرةً بدلاً من مُؤشر (Cursor). إرجاع null يشير إلى عدم العثور على مستند مطابق — وهذا فرق مهم عن find، حيث إن find تُرجع مؤشرًا فارغًا بدلاً من القيمة null.

حالات الاستخدام: الاستعلام عن التفاصيل باستخدام _id، واسترجاع مستند واحد باستخدام فهرس فريد، وإجراء فحص الوجود (لتحديد ما إذا كان المستند يستوفي شرطًا معينًا).

JAVASCRIPT
// === findOne Return to a single document ===
db.products.findOne({ sku: "PHONE-001" });
// Return the first matching document(or null)

// === findOne vs find().limit(1) Difference ===
const doc1 = db.products.findOne({ sku: "PHONE-001" });
const doc2 = db.products.find({ sku: "PHONE-001" }).limit(1).next();
// The results are the same,findOne More concise

(3) مقارنة بين find وfindOne

تحليل النقاط الرئيسية:

  1. find لا يقوم المؤشر الذي يتم إرجاعه بتحميل جميع البيانات على الفور، مما يوفر مساحة الذاكرة
  2. findOne هي في الأساس find().limit(-1)؛ فهي تُرجع المستند مباشرةً، مما يلغي الحاجة إلى إنشاء مؤشر (Cursor).
  3. في Mongoose، تُرجع الدالة find مصفوفة Array<T>، وتُرجع الدالة findOne كائنًا T | null
  4. لتحديد ما إذا كان المستند موجودًا أم لا، فإن استخدام findOne + التحقق من وجود قيمة فارغة أكثر كفاءة من استخدام find + التحقق من طول المصفوفة.
البعد find findOne
نوع القيمة المرجعة مؤشر مستند أو null
عدد المباريات جميع المباريات المباراة الأولى
استخدام الذاكرة التحميل التدفقي (التحميل المؤجل) لمرة واحدة
الأداء سريع أسرع قليلاً (لا يُنشئ مؤشراً)
حالات الاستخدام استعلام القائمة استعلام التفاصيل

(4) نتائج الاستعلام في Mongoose

JAVASCRIPT
// === mongoose: find returns an array ===
const products = await Product.find({ category: "Electronics" });
// Array<Product>

// === mongoose: findOne returns an object ===
const product = await Product.findOne({ sku: "PHONE-001" });
// Product | null

// === Handling Cases Where Query Results Are Empty ===
const product = await Product.findOne({ sku: "NOT_EXIST" });
if (!product) {
  return res.status(404).json({ error: "Product not found" });
}

▶ المثال 1: الاستخدام الكامل لـ find

JAVASCRIPT
// === Search in mongosh ===
// Search All Documents
db.products.find();

// Query by Specified Criteria
db.products.find({ category: "Electronics" });

// Multi-Condition Query(AND)
db.products.find({
  category: "Electronics",
  stock: { $gt: 0 }     // Inventory greater than 0
});

// Look Up and Format
db.products.find({ category: "Electronics" }).pretty();

// Query and Count
db.products.find({ category: "Electronics" }).count();

// === Search in Node.js ===
const { MongoClient } = require('mongodb');

async function findProducts() {
  const client = new MongoClient('mongodb://localhost:27017');
  await client.connect();
  const collection = client.db('shopdb').collection('products');

  // 1. find() Back Cursor
  const cursor = collection.find({ category: "Electronics" });
  const products = await cursor.toArray();
  console.log(`Found ${products.length} products`);

  // 2. findOne() Back Document
  const product = await collection.findOne({ sku: "PHONE-001" });
  console.log(product);

  // 3. Iterate Cursor(Flow)
  for await (const doc of collection.find({ category: "Electronics" })) {
    console.log(doc.title);
  }

  await client.close();
}

findProducts();

الإخراج:

TEXT 📖 للعرض فقط
Found 3 products
{ _id: ObjectId('...'), sku: 'PHONE-001', title: 'Smartphone X', price: NumberDecimal('599.99'), ... }
Smartphone X
Smartphone Y
Smartphone Z


4. عوامل تصفية الاستعلامات

شرح المفهوم: يُعد مرشح الاستعلام المعلمة الأولى في find / findOne، ويُستخدم لتحديد شروط المطابقة. تستخدم المرشحات صيغة JSON/BSON وتدعم أنماطًا متنوعة، بما في ذلك المطابقات الدقيقة، وعوامل المقارنة، والتركيبات المنطقية، والاستعلامات المتداخلة. ويُعد فهم صيغة المرشح أساسًا لإجراء الاستعلامات في MongoDB.

كيفية العمل: يقوم MongoDB بترجمة معايير تصفية الاستعلامات إلى خطة استعلام، ويقوم بمطابقة المستندات باستخدام الفهارس أو عمليات المسح الكامل للجداول. يمكن لكل شرط حقل في معيار التصفية استخدام فهرس بشكل مستقل؛ وعندما يتم الجمع بين عدة شروط، يقوم مُحسِّن MongoDB تلقائيًا باختيار مسار التنفيذ الأمثل.

100%
graph TB
    A[Query Filters] --> B[Exact Match<br/>{ field: value }]
    A --> C[Comparison Operators<br/>{ field: { $gt: N } }]
    A --> D[Logic Combinations<br/>{ $and / $or / $not }]
    A --> E[Nested Queries<br/>{ "path.field": value }]
    A --> F[Array Lookup<br/>{ array: value }]

    style A fill:#cce5ff
نوع المرشح الصيغة مثال
المطابقة التامة { field: value } { sku: "PHONE-001" }
الشرط المتعدد AND { f1: v1, f2: v2 } { category: "E", stock: 50 }
الحقل غير موجود { field: { $exists: false } } { discount: { $exists: false } }
وثيقة متداخلة { "path.field": value } { "specs.battery": "4500mAh" }
عنصر المصفوفة { array: value } { tags: "5g" }

(1) التصفية الأساسية

JAVASCRIPT
// === Exact Match ===
db.products.find({ sku: "PHONE-001" });

// === Multiple conditions AND ===
db.products.find({
  category: "Electronics",
  stock: 50,
  isActive: true
});

// === Field does not exist ===
db.products.find({ discount: { $exists: false } });

// === Nested Document Query ===
db.products.find({ "specs.battery": "4500mAh" });

// === Array Element Matching ===
db.products.find({ tags: "5g" });

// === Matching Multiple Elements in an Array ===
db.products.find({ tags: { $all: ["5g", "amoled"] } });

(2) عوامل المقارنة

نظرة عامة على المفهوم: تشكل عوامل المقارنة جوهر عوامل تصفية الاستعلامات، حيث تدعم عمليات مثل استعلامات النطاق، ومطابقة القيم المتعددة، وعمليات الاستبعاد. توفر MongoDB ثمانية عوامل مقارنة: $eq، $ne، $gt، $gte، $lt، $lte، $in، $nin. من بين هذه العوامل، يُعد $eq السلوك الافتراضي ({ price: 599 } يعادل { price: { $eq: 599 } })، ويُعد $in العامل الأكثر استخدامًا.

المشغل المعنى ما يعادله في لغة SQL مناسب للفهرسة
$eq يساوي WHERE field = value
$ne لا يساوي WHERE field != value ⚠️
$gt/$gte أكبر من / أكبر من أو يساوي WHERE field > />= value
$lt/$lte أقل من / أقل من أو يساوي WHERE field < /<= value
$in مضمن في WHERE field IN (...)
$nin غير مشمول WHERE field NOT IN (...) ⚠️
JAVASCRIPT
// === $eq(equals,Default)===
db.products.find({ price: { $eq: 599.99 } });
// equivalent to { price: 599.99 }

// === $ne(is not equal to)===
db.products.find({ category: { $ne: "Books" } });

// === $gt / $gte(greater than / Greater than or equal to)===
db.products.find({ price: { $gt: 100 } });        // > 100
db.products.find({ price: { $gte: 100 } });       // >= 100

// === $lt / $lte(Less than / Less than or equal to)===
db.products.find({ price: { $lt: 1000 } });
db.products.find({ price: { $lte: 1000 } });

// === $in / $nin(Includes / Excludes)===
db.products.find({ category: { $in: ["Electronics", "Books"] } });
db.products.find({ category: { $nin: ["Clothing"] } });

// === Range Query ===
db.products.find({
  price: { $gte: 100, $lte: 1000 }  // 100 <= price <= 1000
});

(3) العوامل المنطقية

شرح المفهوم: تعمل العوامل المنطقية على دمج شروط استعلام متعددة لتنفيذ منطق تصفية معقد. يدعم MongoDB أربعة عوامل منطقية: $and (يجب استيفاء جميع الشروط)، و$or (يجب استيفاء أي شرط)، و$not (يجب عدم استيفاء أي شرط)، و$nor (يجب عدم استيفاء أي شرط). ومن بين هذه العوامل، يُعد «AND» الضمني (فصل الحقول المتعددة بفواصل) هو الصيغة الأكثر استخدامًا؛ أما $and الصريح فلا يُطلب إلا عندما «تنطبق شروط متعددة على الحقل نفسه».

المشغل المعنى ما يعادله في لغة SQL تكرار الاستخدام
"AND" الضمني مفصولة بفاصلة WHERE a=1 AND b=2 ⭐⭐⭐ الأكثر استخدامًا
$and «و» صريحة WHERE (a=1 AND b=2) ⭐ شروط متعددة لنفس الحقل
$or أي منها يفي WHERE a=1 OR b=2 ⭐⭐
$not غير راضٍ WHERE NOT (condition)
$nor لم يتم استيفاء أي منها WHERE NOT (a=1 OR b=2) غير مستغلة بالكامل
JAVASCRIPT
// === $and(Implicit AND)===
db.products.find({
  category: "Electronics",
  stock: { $gt: 0 }    // Implicit AND
});

// === $and(Explicit AND)===
db.products.find({
  $and: [
    { category: "Electronics" },
    { $or: [{ stock: { $gt: 10 } }, { isFeatured: true }] }
  ]
});

// === $or ===
db.products.find({
  $or: [
    { category: "Electronics" },
    { tags: "bestseller" }
  ]
});

// === $not ===
db.products.find({ price: { $not: { $gt: 1000 } } });
// Price <= 1000

// === $nor(None of them match)===
db.products.find({
  $nor: [
    { category: "Electronics" },
    { category: "Books" }
  ]
});

▶ المثال 2: مثال على استعلام مركب

JAVASCRIPT
// === Scene:Check Prices 100-1000、Inventory greater than 0、Electronics or Books Categorized Products ===
db.products.find({
  price: { $gte: 100, $lte: 1000 },
  stock: { $gt: 0 },
  $or: [
    { category: "Electronics" },
    { category: "Books" }
  ],
  isActive: true
}).sort({ price: 1 }).limit(20);

// === mongoose Equivalent Notation ===
const products = await Product.find({
  price: { $gte: 100, $lte: 1000 },
  stock: { $gt: 0 },
  $or: [{ category: "Electronics" }, { category: "Books" }],
  isActive: true
})
  .sort({ price: 1 })
  .limit(20)
  .lean();

الإخراج:

TEXT 📖 للعرض فقط
[
  { _id: ObjectId('...'), sku: 'PHONE-001', title: 'Smartphone X', price: 299.99, category: 'Electronics', stock: 50 },
  { _id: ObjectId('...'), sku: 'PHONE-002', title: 'Smartphone Y', price: 399.99, category: 'Electronics', stock: 30 },
  { _id: ObjectId('...'), sku: 'BOOK-001', title: 'MongoDB Guide', price: 199.99, category: 'Books', stock: 100 }
]


5. الإسقاط

شرح المفهوم: تعمل «الإسقاط» (Projection) على التحكم في الحقول التي يتم إرجاعها من خلال الاستعلام، وهي تقنية تحسين أساسية لتقليل حركة مرور الشبكة والحمل على عملية التحليل في الواجهة الأمامية. بشكل افتراضي، تُرجع MongoDB جميع الحقول الموجودة في المستند، ولكن في حالات مثل صفحات القوائم واستجابات واجهة برمجة التطبيقات (API)، لا تكون هناك حاجة عادةً سوى إلى 3–5 حقول رئيسية. ويمكن أن يؤدي الاستخدام السليم لـ «الإسقاط» إلى تقليل حجم الاستجابة بأكثر من 90%.

كيفية العمل: يتم تنفيذ عملية الإسقاط من جانب الخادم — بعد أن يقرأ MongoDB المستند بأكمله، يقوم بتقليص الحقول وفقًا لقواعد الإسقاط قبل إرجاع النتيجة. وهذا يعني أن عملية الإسقاط لا تقلل من عمليات الإدخال/الإخراج على القرص (حيث لا يزال يتعين قراءة المستند بأكمله)، ولكنها يمكن أن تقلل بشكل كبير من حركة مرور الشبكة ووقت إزالة التسلسل لدى العميل. الاستثناء الوحيد هو «الاستعلام المشمول» (Covered Query) — عندما تكون جميع الحقول في الاستعلام وعملية الإسقاط مضمنة في فهرس، يقوم MongoDB بإرجاع البيانات مباشرةً من الفهرس دون الحاجة إلى قراءة المستند.

100%
graph LR
    A[Complete Documentation<br/>20 field ~5KB] --> B{Projection Rules}
    B -->|Whitelist Mode<br/>{ sku: 1, title: 1, price: 1 }| C[3 field ~200B]
    B -->|Blacklist Mode<br/>{ description: 0, images: 0 }| D[18 field ~4.5KB]
    
    style C fill:#d4edda
وضع العرض بناء الجملة الميزات حالات الاستخدام
القائمة البيضاء { field: 1 } تعرض الحقول المحددة فقط صفحة القائمة (تتطلب حقولًا قليلة)
القائمة السوداء { field: 0 } استبعاد الحقول المحددة صفحة التفاصيل (استبعاد الحقول الحساسة)
مختلط ❌ غير مسموح لا يمكن استخدام القوائم البيضاء والقوائم السوداء معًا (باستثناء _id)
التحكم في _id { _id: 0 } يتم إرجاعه افتراضيًا؛ يجب استبعاده صراحةً إزالة _id من استجابة واجهة برمجة التطبيقات

(1) ما المقصود بالإسقاط؟

تقوم وظيفة التحكم في العرض بإرجاع حقول محددة، مما يقلل من العبء على عملية الإرسال عبر الشبكة وعملية التحليل في الواجهة الأمامية.

100%
graph LR
    A[Complete Documentation<br/>20 field] --> B{Projection}
    B -->|Field Whitelist| C[Return only 3 field<br/>~10 KB]
    B -->|Field Blacklist| D[Exclusion 2 field<br/>~18 KB]

    style C fill:#d4edda

(2) قواعد بناء الجمل في الإسقاط

JAVASCRIPT
// === Field Whitelist(Return only the specified fields)===
db.products.find(
  { category: "Electronics" },
  { sku: 1, title: 1, price: 1 }
);
// Back:{ _id, sku, title, price }

// === _id Return by Default,Must be explicitly excluded ===
db.products.find(
  {},
  { sku: 1, title: 1, _id: 0 }  // _id: 0 Exclusion _id
);

// === Field Blacklist(Exclude Specified Fields)===
db.products.find(
  {},
  { internalNotes: 0, debugInfo: 0 }  // Exclude Sensitive Fields
);

// === Nested Document Projection ===
db.products.find(
  { sku: "PHONE-001" },
  {
    sku: 1,
    title: 1,
    "specs.screen": 1,    // Return only specs.screen
    "specs.battery": 1   // Return only specs.battery
  }
);

// === Array Element Projection($slice)===
db.reviews.find(
  { productId: "PHONE-001" },
  {
    title: 1,
    content: 1,
    comments: { $slice: 3 }  // Return only the first 3 Comments
  }
);

(3) التأثيرات على أداء العرض

JAVASCRIPT
// === Performance Testing:100 10,000 Documents,Search 100 items ===

// ❌ No projection:Back 5MB
db.products.find({ category: "Electronics" }).limit(100);
// Time taken 800ms

// ✅ Projection available:Back 200KB
db.products.find(
  { category: "Electronics" },
  { sku: 1, title: 1, price: 1, _id: 0 }
).limit(100);
// Time taken 80ms(Performance ↑10x)

(4) العرض في مونغوس

JAVASCRIPT
// === Methods 1:projection option ===
const products = await Product.find({ category: "Electronics" }, "sku title price");
// String Syntax(Space-separated)

// === Methods 2:select() Chain-type ===
const products = await Product.find()
  .select("sku title price")
  .select("-description -images");  // Exclude certain fields

// === Methods 3:Object Syntax ===
const products = await Product.find(
  { category: "Electronics" },
  { sku: 1, title: 1, price: 1, _id: 0 }
);

// === Methods 4:lean() + select() Optimal Performance ===
const products = await Product.find()
  .select("sku title price")
  .lean()  // Skip mongoose hydrate
  .limit(100);

▶ المثال 3: أفضل الممارسات الخاصة بواجهات برمجة التطبيقات (API) لقوائم التجارة الإلكترونية

JAVASCRIPT
// === Complete List of E-commerce Sites API ===
app.get('/api/products', async (req, res) => {
  const {
    category,
    minPrice,
    maxPrice,
    search,
    sort = 'createdAt',
    order = 'desc',
    page = 1,
    limit = 20
  } = req.query;

  // 1. Build Query Criteria
  const query = { isActive: true };
  if (category) query.category = category;
  if (minPrice || maxPrice) {
    query.price = {};
    if (minPrice) query.price.$gte = NumberDecimal(minPrice);
    if (maxPrice) query.price.$lte = NumberDecimal(maxPrice);
  }
  if (search) query.title = new RegExp(search, 'i');

  // 2. Sort
  const sortObj = { [sort]: order === 'desc' ? -1 : 1 };

  // 3. Pagination
  const skip = (page - 1) * limit;

  // 4. Search(With Projector + lean)
  const products = await Product.find(query)
    .select('sku title price thumbnail rating reviewCount')  // Return only 6 field
    .sort(sortObj)
    .skip(skip)
    .limit(Number(limit))
    .lean();   // Key:Skip mongoose hydrate

  // 5. Total Count
  const total = await Product.countDocuments(query);

  res.json({
    products,
    pagination: {
      page: Number(page),
      limit: Number(limit),
      total,
      pages: Math.ceil(total / limit)
    }
  });
});

الإخراج:

TEXT 📖 للعرض فقط
{
  "products": [
    { "_id": "...", "sku": "PHONE-001", "title": "Smartphone X", "price": 599.99, "thumbnail": "...", "rating": 4.5, "reviewCount": 128 },
    { "_id": "...", "sku": "PHONE-002", "title": "Smartphone Y", "price": 499.99, "thumbnail": "...", "rating": 4.2, "reviewCount": 95 }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 150,
    "pages": 8
  }
}


6. دالة pretty() وتنسيق النتائج

شرح المفهوم: pretty() هي طريقة تنسيق في mongosh تعمل على تحويل مخرجات JSON المضغوطة إلى تنسيق قابل للقراءة ومتراجع. ولا تؤثر هذه الطريقة على منطق الاستعلام أو البيانات التي يتم إرجاعها؛ بل تقتصر على تغيير طريقة عرض المخرجات في محطة mongosh. في تنفيذ البرامج النصية ورموز Node.js، لا تعمل pretty() — يجب عليك استخدام printjson() أو JSON.stringify(obj, null, 2) لتحقيق تأثير مشابه.

طريقة التنسيق البيئة الوصف
.pretty() وضع التفاعل في Mongosh تخطيط متراجع، أفضل قابلية للقراءة
printjson() برنامج مونغوش إخراج بنية JSON كاملة
JSON.stringify(obj, null, 2) Node.js التنسيق القياسي لـ JSON
console.dir(obj, { depth: null }) Node.js الإخراج الكامل للهياكل المتداخلة بعمق

(1) تنسيق المخرجات باستخدام pretty()

JAVASCRIPT
// === Default Output (compact)===
db.products.findOne({ sku: "PHONE-001" });
// { _id: ObjectId('...'), sku: 'PHONE-001', title: 'Phone', ... }

// === pretty() Format ===
db.products.findOne({ sku: "PHONE-001" }).pretty();
// {
//   _id: ObjectId('507f1f77bcf86cd799439011'),
//   sku: 'PHONE-001',
//   title: 'Smartphone X',
//   price: NumberDecimal('599.99'),
//   ...
// }

// === find Also supported pretty ===
db.products.find({ category: "Electronics" }).pretty();

(2) تأثير كلمة «pretty» في البرامج النصية

BASH
# pretty Active in interactive mode,No differences in the script output
mongosh "mongodb://localhost:27017" --eval "db.products.find().pretty()"

(3) التنسيق المخصص

JAVASCRIPT
// === Usage printjson() ===
db.products.find().forEach(printjson);
// Output the complete JSON Structure

// === Usage tojson() ===
const doc = db.products.findOne();
print(tojson(doc));

// === Format the output(pretty 2)===
printjson(doc, null, 2);


7. تحديد / تخطي / فرز

شرح المفهوم: limit وskip وsort هي الطرق الثلاث الرئيسية لتعديل نتائج الاستعلام؛ فهي تتحكم في عدد النتائج المعروضة، وعدد النتائج المراد تخطيها، وقاعدة الفرز، على التوالي. ترتيب تطبيق هذه الطرق هو sort → skip → limit، بغض النظر عن ترتيب كتابتها في الكود — حيث يقوم MongoDB دائمًا بالفرز أولاً، ثم تخطي النتائج، وأخيرًا تحديد عدد النتائج.

كيفية العمل: sort يتطلب من MongoDB فرز المستندات المطابقة قبل إرجاع النتائج. إذا كان حقل الفرز مفهرسًا، فإنه يستخدم ترتيب الفهرس (كفاءة عالية)؛ وإلا، فإنه يقوم بالفرز في الذاكرة (سيحدث خطأ إذا تجاوز الحجم 32 ميغابايت). skip(N) يتطلب مسح المستندات N الأولى وتجاهلها؛ وكلما زاد N، انخفض الأداء — وهذا هو السبب الجذري لمشكلة الترقيم العميق للصفحات. limit(N) يحد من عدد المستندات التي يتم إرجاعها، مما يسمح بإنهاء عملية المسح مبكرًا.

100%
graph TB
    A[Query Results Set<br/>1000 Matches found] --> B[sort Sort<br/>By specified field]
    B --> C[skip Skip<br/>First N items]
    C --> D[limit Excerpt<br/>Return M items]
    
    B --> B1{The sort field is indexed?}
    B1 -->|Have| B2[Index Scan<br/>O(log N)]
    B1 -->|No| B3[Memory Sorting<br/>O(N log N)<br/>More than 32MB throws error]
    
    style B2 fill:#d4edda
    style B3 fill:#f8d7da
الطريقة الوظيفة التأثير على الأداء الاحتياطات
sort({ field: 1/-1 }) الفرز الفرز في الذاكرة دون استخدام فهرس 1: ترتيب تصاعدي، -1: ترتيب تنازلي
skip(N) تخطي أول N سجلات كلما زاد N، زادت بطء العملية تجنب استخدام الترقيم العميق للصفحات
limit(N) تحديد عدد النتائج تحسين الكفاءة الموصى به: ≤ 100

(1) limit: تحديد عدد النتائج المعروضة

JAVASCRIPT
// === Back 10 items ===
db.products.find().limit(10);

// === Conditions for Cooperation ===
db.products.find({ category: "Electronics" }).limit(5);

// === limit(0) equivalent to limit(1) ===
db.products.find().limit(0);  // Back 1 items

// === limit(-1) Return All (Special)===
db.products.find().limit(-1);  // Back to All(Used as a reverse sort)

(2) تخطي تخطي المستند

JAVASCRIPT
// === Skip to the beginning 10 items,Back to Page 11-20 items ===
db.products.find().skip(10).limit(10);

// === Page Numbering Formula ===
// Page N(per page 20 items):skip = (N - 1) * 20
db.products.find().skip((page - 1) * 20).limit(20);

// === skip + sort Consistency ===
db.products.find().sort({ _id: 1 }).skip(10).limit(10);

(3) الفرز

JAVASCRIPT
// === Ascending (1)===
db.products.find().sort({ price: 1 });     // Price (ascending)

// === Descending (-1)===
db.products.find().sort({ createdAt: -1 }); // Latest First

// === Sorting by Multiple Fields ===
db.products.find().sort({ category: 1, price: -1 });
// Press first category ascending,Press again price descending

// === Sorting Nested Fields ===
db.products.find().sort({ "specs.rating": -1 });

// === Sorting Array Fields ===
db.products.find().sort({ "tags.0": 1 });  // by tags Sorting the First Element

(4) مزيج من «تحديد» / «تخطي» / «فرز»

JAVASCRIPT
// === Full Paginated Query ===
db.products
  .find({ category: "Electronics", isActive: true })
  .sort({ price: 1, createdAt: -1 })  // Price (ascending),Reverse Chronological Order
  .skip(20)                            // Skip 20 items
  .limit(10);                          // Back 10 items

// === mongoose Equivalent Notation ===
const products = await Product
  .find({ category: "Electronics", isActive: true })
  .sort({ price: 1, createdAt: -1 })
  .skip(20)
  .limit(10)
  .lean();

(5) تحسين أداء ترقيم الصفحات

شرح المفهوم: تعاني طريقة ترقيم الصفحات التقليدية skip + limit من انخفاض حاد في الأداء عند التنقل إلى أعماق ترقيم الصفحات — حيث تتطلب skip(10000) مسح 10,000 مستند قبل استبعادها. أما الترقيم القائم على المؤشر فيستخدم _id أو مفتاح فرز لتحديد الموضع الابتدائي والانتقال مباشرةً إلى المستند المستهدف، وبالتالي لا يتأثر أدائه بعمق الترقيم.

التحليل المقارن:

البعد تخطي + الحد ترقيم الصفحات بالمؤشر
أداء عميق لتقليب الصفحات ❌ O(N) خطي تنازلي ✅ O(log N) مستقر
دعم الانتقال بين الصفحات ✅ أي رقم صفحة ❌ التنقل للأمام وللخلف فقط
العدد الإجمالي يتطلب استخدام countDocuments غير مطلوب
حالات الاستخدام إدارة الخلفية (التنقل بين الصفحات) التمرير اللانهائي، موجز الأخبار
100%
graph TB
    A[Pagination] --> B[skip + limit Traditional]
    A --> C[Cursor-Based Pagination<br/>Recommendations]

    B --> B1[skip(10000) Slow<br/>Scan 10000 items]
    C --> C1[lastId Search<br/>Direct Targeting]

    style C1 fill:#d4edda
JAVASCRIPT
// === Traditional Pagination(Slow page turns)===
const page1 = await Product.find().skip(0).limit(20);
const page1000 = await Product.find().skip(20000).limit(20);  // Slow!

// === Cursor-Based Pagination(Recommendations)===
const lastId = null;  // The First Time
const products1 = await Product.find({ _id: { $gt: lastId } }).limit(20);

const nextLastId = products1[products1.length - 1]._id;
const products2 = await Product.find({ _id: { $gt: nextLastId } }).limit(20);
// Stable performance,Not affected by page depth

▶ المثال 4: ترقيم الصفحات الكامل + الفرز

JAVASCRIPT
// === Comprehensive Practical Training:Pagination of Product List API ===
app.get('/api/products', async (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = Math.min(parseInt(req.query.limit) || 20, 100);
  const sortBy = req.query.sort || 'createdAt';
  const order = req.query.order === 'asc' ? 1 : -1;

  const products = await Product.find({ isActive: true })
    .select('sku title price thumbnail rating')
    .sort({ [sortBy]: order })
    .skip((page - 1) * limit)
    .limit(limit)
    .lean();

  const total = await Product.countDocuments({ isActive: true });

  res.json({
    data: products,
    pagination: {
      page,
      limit,
      total,
      pages: Math.ceil(total / limit),
      hasNext: page * limit < total,
      hasPrev: page > 1
    }
  });
});

الإخراج:

TEXT 📖 للعرض فقط
{
  "data": [
    { "_id": "...", "sku": "PHONE-001", "title": "Smartphone X", "price": 599.99, "thumbnail": "...", "rating": 4.5 },
    { "_id": "...", "sku": "PHONE-002", "title": "Smartphone Y", "price": 499.99, "thumbnail": "...", "rating": 4.2 }
  ],
  "pagination": {
    "page": 1,
    "limit": 10,
    "total": 150,
    "pages": 15,
    "hasNext": true,
    "hasPrev": false
  }
}


8. عدد المستندات

شرح المفهوم: countDocuments وestimatedDocumentCount هما طريقتان للعد في MongoDB. توفر الطريقة الأولى عددًا دقيقًا، لكنها تتطلب مسح المستندات المطابقة، بينما تقوم الطريقة الثانية بتقدير العدد استنادًا إلى بيانات تعريف المجموعة — مما يجعلها سريعة للغاية — لكنها لا تدعم شروط التصفية. ويُعد فهم الفرق بين الطريقتين أمرًا بالغ الأهمية لتقسيم الصفحات في صفحات القوائم وفي السيناريوهات الإحصائية.

كيفية العمل: يقوم countDocuments بتنفيذ خطة استعلام لحساب عدد جميع المستندات المطابقة؛ ويتناسب أدائه تناسبًا مباشرًا مع عدد المطابقات. أما estimatedDocumentCount فيقوم بقراءة البيانات الوصفية للمجموعة (عدد المستندات) مباشرةً دون تنفيذ أي استعلامات؛ ويبلغ أدائه O(1). وفي مجموعات البيانات الكبيرة، قد يصل الفارق في الأداء بين الاثنين إلى أكثر من 100 ضعف.

البعد عدد الوثائق العدد التقديري للوثائق
الدقة ✅ دقيقة ⚠️ تقديرية (الخطأ < 5%)
الأداء ⚠️ بطيء (مسح كامل للجدول) ⚡⚡ سريع للغاية (O(1))
معايير التصفية ✅ مدعومة ❌ غير مدعومة
ملخص شامل ⚠️ بطيء ⚡ سريع
في الوقت الفعلي ✅ في الوقت الفعلي ⚠️ شبه فوري

(1) countDocuments: العدد الدقيق

JAVASCRIPT
// === Count all documents ===
db.products.countDocuments();

الإخراج:

TEXT 📖 للعرض فقط
1250
JAVASCRIPT
// === Statistics Meet the Criteria ===
db.products.countDocuments({ category: "Electronics" });

الإخراج:

TEXT 📖 للعرض فقط
250
JAVASCRIPT
// === With options ===
db.products.countDocuments(
  { category: "Electronics" },
  { limit: 1000 }   // Most Scans 1000 items
);

// === mongoose Equivalent ===
const count = await Product.countDocuments({ category: "Electronics" });
// 250

(2) تقدير عدد المستندات (أسرع)

JAVASCRIPT
// === Estimated Total(Metadata-Based,Extremely fast)===
db.products.estimatedDocumentCount();
// 1250(Approximate value)

// === Applicable Scenarios ===
// - List Page Display"Total 1000 items"(No need for precision)
// - Statistics that do not require real-time processing

// === Performance Comparison ===
// countDocuments({}): ~100ms(Full Table Scan)
// estimatedDocumentCount(): ~1ms(Read Metadata)

(3) countDocuments مقابل estimatedDocumentCount

البعد عدد الوثائق العدد التقديري للوثائق
الدقة ✅ دقيقة ⚠️ تقديرية (الخطأ < 5%)
الأداء ⚠️ بطيء (مسح كامل للجدول) ⚡⚡ سريع للغاية (O(1))
معايير التصفية ✅ مدعومة ❌ غير مدعومة
ملخص شامل ⚠️ بطيء ⚡ سريع
في الوقت الفعلي ✅ في الوقت الفعلي ⚠️ شبه فوري

▶ المثال 5: الاستخدام العملي لـ count

JAVASCRIPT
// === Product Category Statistics(Accurate)===
const stats = await Product.aggregate([
  { $group: { _id: "$category", count: { $sum: 1 } } },
  { $sort: { count: -1 } }
]);
// [
//   { _id: 'Electronics', count: 250 },
//   { _id: 'Books', count: 200 },
//   { _id: 'Clothing', count: 180 }
// ]

// === Total Number of List Pages(Estimate)===
const totalProducts = await Product.estimatedDocumentCount();
const electronicsCount = await Product.countDocuments({ category: "Electronics" });

res.json({
  total: totalProducts,        // Estimate 1250
  electronics: electronicsCount // Accurate 250
});

الإخراج:

TEXT 📖 للعرض فقط
[
  { "_id": "Electronics", "count": 250 },
  { "_id": "Books", "count": 200 },
  { "_id": "Clothing", "count": 180 }
]


9. معالجة نتائج الاستعلام

(1) تكرار المؤشر

JAVASCRIPT
// === mongosh Iterate through the middle ===
db.products.find({ category: "Electronics" }).forEach(doc => {
  print(`SKU: ${doc.sku}, Title: ${doc.title}`);
});

// === Node.js Iterate through the middle ===
const cursor = collection.find({ category: "Electronics" });

// Methods 1:toArray()
const products = await cursor.toArray();

// Methods 2:for await...of
for await (const doc of collection.find({ category: "Electronics" })) {
  console.log(doc.title);
}

// Methods 3:Manual next()
const cursor2 = collection.find({ category: "Electronics" });
while (await cursor2.hasNext()) {
  const doc = await cursor2.next();
  console.log(doc);
}

(2) تهيئة المؤشر

JAVASCRIPT
// === Set the batch size ===
const cursor = collection.find({ category: "Electronics" })
  .batchSize(100);  // Per batch 100 items

// === Limit the Maximum Return ===
const cursor = collection.find({ category: "Electronics" })
  .limit(1000);

// === Limit Cursor Timeout ===
const cursor = collection.find({ category: "Electronics" })
  .maxTimeMS(5000);  // 5 Timeout in seconds

(3) سلاسل الاستعلامات في Mongoose

JAVASCRIPT
// === Complete mongoose Query Chain ===
const products = await Product.find({ category: "Electronics" })
  .where('price').gt(100).lt(1000)        // Price 100-1000
  .where('stock').gt(0)                   // In stock
  .select('sku title price')              // Projection
  .sort({ price: 1 })                     // Sort
  .skip(20)                               // Pagination
  .limit(10)                              // Restrictions
  .populate('categoryId', 'name slug')    // Joined Queries
  .lean();                                // Performance Optimization

// === Equivalent, concise notation ===
const products2 = await Product.find({
  category: "Electronics",
  price: { $gt: 100, $lt: 1000 },
  stock: { $gt: 0 }
})
  .select('sku title price')
  .sort({ price: 1 })
  .skip(20)
  .limit(10)
  .lean();

▶ المثال 6: دليل عملي للاستعلامات المركبة

JAVASCRIPT
// === Scene:E-commerce Product Search API ===
app.get('/api/products/search', async (req, res) => {
  const { q, category, minPrice, maxPrice, sortBy = 'relevance' } = req.query;

  // 1. Build a Query
  const query = { isActive: true };
  if (q) query.$text = { $search: q };
  if (category) query.category = category;
  if (minPrice || maxPrice) {
    query.price = {};
    if (minPrice) query.price.$gte = NumberDecimal(minPrice);
    if (maxPrice) query.price.$lte = NumberDecimal(maxPrice);
  }

  // 2. Sort
  const sortObj = sortBy === 'price_asc' ? { price: 1 } :
                  sortBy === 'price_desc' ? { price: -1 } :
                  sortBy === 'newest' ? { createdAt: -1 } :
                  { score: { $meta: 'textScore' } };  // Full-Text Search with Relevance Ranking

  // 3. Search
  const products = await Product.find(query, sortObj.score ? { score: { $meta: 'textScore' } } : {})
    .sort(sortObj)
    .limit(40)
    .lean();

  // 4. Statistics
  const total = await Product.countDocuments(query);

  res.json({
    query: { q, category, minPrice, maxPrice },
    total,
    products
  });
});

الإخراج:

TEXT 📖 للعرض فقط
{
  "query": { "q": "phone", "category": "Electronics", "minPrice": "100", "maxPrice": "1000" },
  "total": 45,
  "products": [
    { "_id": "...", "sku": "PHONE-001", "title": "Smartphone X", "price": 599.99, "score": 2.5 },
    { "_id": "...", "sku": "PHONE-002", "title": "Smartphone Y", "price": 499.99, "score": 2.3 }
  ]
}


❓ أسئلة شائعة

س ما مدى الفرق في الأداء بين find وfindOne؟
ج تقوم find بإنشاء مؤشر (تحميل مؤجل)، بينما تُرجع findOne مستندًا مباشرةً. الفرق في الأداء ضئيل (< 5٪)، لكن findOne أبسط. استخدم find للقوائم وfindOne للتفاصيل.
س هل يتم إرجاع الحقل _id بشكل افتراضي؟
ج نعم. _id هو حقل مضمن بشكل افتراضي ويجب استبعاده صراحةً _id: 0. وإلا، فحتى لو تم حذف _id من عملية العرض، فسيتم إرجاعه على أي حال.
س هل يمكن أن تقلل الإسقاطات من وقت الاستعلام؟
ج نعم، ولكن إلى حد محدود فقط. تحتاج MongoDB إلى قراءة المستند بأكمله لتطبيق الإسقاط (ما لم يتم استخدام استعلام مغطى). وتكمن التحسينات الرئيسية في وقت نقل البيانات عبر الشبكة ووقت إزالة التسلسل؛ أما التأثير على وقت تنفيذ الاستعلام فهو ضئيل للغاية.
س لماذا يكون countDocuments بطيئًا؟
ج يتطلب الحساب الدقيق مسح جميع المستندات المطابقة. بالنسبة للمجموعات الكبيرة، نوصي باستخدام estimatedDocumentCount() (استنادًا إلى البيانات الوصفية) أو إرجاع قيمة تقديرية في واجهة برمجة تطبيقات ترقيم الصفحات.
س هل يتباطأ أداء skip كلما تعمقت في البحث؟
ج نعم! يحتاج skip(N) إلى مسح أول N مستندات قبل إرجاع النتائج، لذا كلما زاد N، زاد بطء الأداء. بالنسبة للترقيم العميق للصفحات، نوصي باستخدام ترقيم الصفحات بالمؤشر ({ _id: { $gt: lastId } }).
س هل أحتاج إلى إنشاء فهرس لحقل الفرز؟
ج يُنصح بذلك بشدة. وإلا، فسيضطر MongoDB إلى إجراء عملية الفرز في الذاكرة، وإذا تجاوز الحجم 32 ميغابايت، فسيحدث خطأ Sort exceeded memory limit. وبوجود الفهرس، تكون عملية الفرز من الدرجة O(log N).
س ما معنى find().limit(0)؟
ج يُرجع مستندًا واحدًا (وهو سلوك خاص في MongoDB). لإرجاع صفر مستندات، استخدم find({ _id: null }).
س ما الغرض من lean() في Mongoose؟
ج إنه يتخطى عملية تهيئة المستندات في Mongoose ويعيد كائن JavaScript عاديًا مباشرةً. وهذا يوفر تحسّنًا في الأداء بمقدار 3–5 أضعاف، لكنك تفقد إمكانية الوصول إلى أساليب المستندات في Mongoose (مثل save() وpopulate()). وهو مناسب لسيناريوهات الاستعلام البحتة.

📖 ملخص


📝 تمارين

  1. السؤال الأساسي (⭐): أدخل 10 مستندات منتجات في Mongosh، واستخدم find للاستعلام عن جميع المستندات في فئة «الإلكترونيات»، وقم بتنسيق النتيجة باستخدام pretty().

  2. السؤال الأساسي (⭐): استخدم findOne للبحث عن المنتج الذي يحمل رمز SKU "PHONE-001"، واستخدم الإسقاط لإرجاع الحقول الثلاثة التالية فقط: SKU، والعنوان، والسعر.

  3. تمرين متقدم (⭐⭐): اكتب واجهة برمجة تطبيقات (API) لـ Node.js تُنفِّذ استعلامات مقسمة إلى صفحات لقائمة المنتجات (باستخدام المعلمتين page وlimit)، وقم بتحسين الأداء باستخدام projection وlean()، وأعد معلومات التجزئة إلى صفحات (total وpages وhasNext).

  4. سؤال متقدم (⭐⭐): ابحث عن المنتجات التي تتراوح أسعارها بين 100 و1000، ومتوفرة في المخزون (المخزون > 0)، وتندرج ضمن فئتي «الإلكترونيات» أو «الكتب»؛ وقم بفرزها بترتيب تصاعدي حسب السعر، وقصر النتائج على 20 نتيجة.

  5. مشكلة متقدمة (⭐⭐): قارن أوقات تنفيذ الاستعلامات لـ skip(0).limit(20) و skip(10000).limit(20) على مجموعة مكونة من مليون مستند لفهم مشكلة الترقيم العميق للصفحات.

  6. التحدي (⭐⭐⭐): قم بتنفيذ واجهة برمجة تطبيقات (API) لتقسيم الصفحات تعتمد على المؤشر (باستخدام lastId بدلاً من skip) تدعم تقسيم الصفحات بأي عمق دون المساس بالأداء، وتشمل وثائق كاملة لواجهة برمجة التطبيقات وحالات اختبار.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%