如何同时从 MongoDB 使用 Mongoose 查找文档并获取它们的计数。

huangapple go评论67阅读模式
英文:

How simultaneously find documents and get their count from mongodb using mongoose

问题

我的目标是根据特定条件从数据库中获取书籍,同时也想获取它们的数量。以下是我的代码。在这里,我实现了分页,所以我使用limit()和skip()来仅获取特定页面的所需数据。我在标题或作者字段上进行了正则搜索,并选择了给定流派的文档。我还根据查询进行升序或降序排序。

以下是我的模型:

const mongoose = require("mongoose");

const LibrarySchema = mongoose.Schema(
  {
    // 表示书籍的标题
    title: {
      type: String,
      required: true,
    },
    // 表示书籍的作者
    author: {
      type: String,
      required: true,
    },
    genre: {
      type: [String],
      required: true,
    },
  },
  {
    timestamps: true,
  }
);
module.exports = mongoose.model("LibraryModel", LibrarySchema);

目前,我能够通过两次分开的数据库调用获取特定查询的书籍和它们的总数。但是,假设我们的数据库中有成千上万本书,那么这种方法将非常昂贵。因此,我希望将这两个调用合并为一个,并一次获取结果。我尝试寻找解决方案,但未能找到任何解决方法。感谢您提前的帮助。

英文:

My aim is to get books from the database on specific condition but at the same time i want to get their count as well. Below is my code. Here i have implemented pagination so i using limit() and skip() to get only required data for that particular page. I am doing a regex search on either title or author field and selecting documents for given genre. I am also sorting them in ascending or descending based on query.

const getAllBooks = asyncHandler(async (req, res) => {
  const page = req.query.page === undefined ? 1 : req.query.page;
  const limit = 5;
  const skip = page * limit - limit;
  const allGenres = ["action", "drama", "sci - fi", "romance", "comedy"];
  const reqGenre =
    req.query.genre === "" ? allGenres : req.query.genre.split(",");
  const search = req.query.search === undefined ? "" : req.query.search;
  const searchBy =
    req.query.searchBy === "title"
      ? { title: { $regex: search, $options: "i" } }
      : { author: { $regex: search, $options: "i" } };
  const sort = req.query.sort === "asc" ? 1 : -1;
  let sortBy;
  if (req.query.sortBy === undefined) {
    sortBy = { title: sort };
  } else if (req.query.sortBy === "author") {
    sortBy = { author: sort };
  } else if (req.query.sortBy === "title") {
    sortBy = { title: sort };
  }
  const books = await Book.find(searchBy)
    .where("genre")
    .in(reqGenre)
    .sort(sortBy)
    .skip(skip)
    .limit(limit)
    .select("-createdAt -updatedAt -__v");
  const documents = await Book.find(searchBy)
    .where("genre")
    .in(reqGenre)
    .sort(sortBy)
    .count();
  const total = documents / limit;
  res.status(200);
  res.json({ books, total });
})

Below is my model

const mongoose = require("mongoose");

const LibrarySchema = mongoose.Schema(
  {
    //denotes the title of the book
    title: {
      type: String,
      required: true,
    },
    // denotes the author of the book
    author: {
      type: String,
      required: true,
    },
    genre: {
      type: [String],
      required: true,
    },
  },
  {
    timestamps: true,
  }
);
module.exports = mongoose.model("LibraryModel", LibrarySchema);

Currently i am able to get books and their total for specific query on two separate calls to the database. But hypothetically if we have thousands of books in the database then this method will be very expensive. So i want to merge these both calls into one and get the result in one go. I tried to get a solution but could not find any. Any help is appreciated. Thanks in advance.

答案1

得分: 2

I think you can use $facet to generate two outputs:

db.collection.aggregate([
  {
    "$match": {
      // 你的搜索条件在这里
    }
  },
  {
    "$sort": {
      // 你的排序条件在这里
    }
  },
  {
    "$facet": {
      "result": [
        {
          "$skip": // 你的跳过数量
        },
        {
          "$limit": // 你的限制数量
        },
        {
          "$project": {
            // 你的选择字段
          }
        }
      ],
      "count": [
        {
          "$count": "count"
        }
      ]
    }
  },
  {
    "$project": {
      "result": 1,
      "count": {
        "$arrayElemAt": [
          "$count",
          0
        ]
      }
    }
  }
])

示例 在这里 包含了预定义的查询值,你可以使用你已经创建的对象。

你可以通过你拥有的JS对象来更改聚合对象,应该能正常工作。

用于动态创建查询的JS代码段:

// 模拟请求以避免问题
const req = {
    query: {
        genre: ""
    }
}

const page = req.query.page === undefined ? 1 : req.query.page;
const limit = 5;
const skip = page * limit - limit;
const allGenres = ["action", "drama", "sci-fi", "romance", "comedy"];
const reqGenre = req.query.genre === "" ? allGenres : req.query.genre.split(",");
const search = req.query.search === undefined ? "" : req.query.search;
let matchQuery = { "$match": {} }
const searchBy = req.query.searchBy === "title"
    ? matchQuery["$match"] = { title: { $regex: search, $options: "i" } }
    : matchQuery["$match"] = { author: { $regex: search, $options: "i" } };
matchQuery["$match"]["genre"] = { "$in": reqGenre }
const sort = req.query.sort === "asc" ? 1 : -1;
let sortBy;
if (req.query.sortBy === undefined) {
    sortBy = { title: sort };
} else if (req.query.sortBy === "author") {
    sortBy = { author: sort };
} else if (req.query.sortBy === "title") {
    sortBy = { title: sort };
}
const query = [matchQuery,
  {
    "$sort": sortBy
  },
  {
    "$facet": {
      "result": [
        {
          "$skip": skip
        },
        {
          "$limit": limit
        },
        {
          "$project": {
            "createdAt": 0,
            "-createdAt": 0,
            "-updatedAt": 0,
            "-__v": 0
          }
        }
      ],
      "count": [
        {
          "$count": "count"
        }
      ]
    }
  },
  {
    "$project": {
      "result": 1,
      "count": {
        "$arrayElemAt": [
          "$count",
          0
        ]
      }
    }
  }]

console.log(JSON.stringify(query))

这段代码创建了查询,因此你可以简单地执行 const result = await Book.aggregate(query)

注意 console.log() 输出是一个有效的Mongo查询,可以在此示例中使用。

英文:

I think you can use $facet to generate two outputs:

db.collection.aggregate([
  {
    "$match": {
      // your searchBy query here
  },
  {
    "$sort": {
      // your sortBy query here
    }
  },
  {
    "$facet": {
      "result": [
        {
          "$skip": // your skip
        },
        {
          "$limit": // your limit
        },
        {
          "$project": {
            // your select
          }
        }
      ],
      "count": [
        {
          "$count": "count"
        }
      ]
    }
  },
  {
    "$project": {
      "result": 1,
      "count": {
        "$arrayElemAt": [
          "$count",
          0
        ]
      }
    }
  }
])

Example here with predefined values in the quey, you can use the objects you have created.

You can change aggregate objects by JS objects you have and should works fine.

JS snippet to create the query dinamically:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

// mock req to avoid problems
const req = {
query: {
genre: &quot;&quot;
}
}
const page = req.query.page === undefined ? 1 : req.query.page;
const limit = 5;
const skip = page * limit - limit;
const allGenres = [&quot;action&quot;, &quot;drama&quot;, &quot;sci - fi&quot;, &quot;romance&quot;, &quot;comedy&quot;];
const reqGenre = req.query.genre === &quot;&quot; ? allGenres : req.query.genre.split(&quot;,&quot;);
const search = req.query.search === undefined ? &quot;&quot; : req.query.search;
let matchQuery = {&quot;$match&quot;:{}}
const searchBy = req.query.searchBy === &quot;title&quot;
? matchQuery[&quot;$match&quot;] = { title: { $regex: search, $options: &quot;i&quot; } }
: matchQuery[&quot;$match&quot;] = { author: { $regex: search, $options: &quot;i&quot; } };
matchQuery[&quot;$match&quot;][&quot;genre&quot;] = {&quot;$in&quot;: reqGenre}
const sort = req.query.sort === &quot;asc&quot; ? 1 : -1;
let sortBy;
if (req.query.sortBy === undefined) {
sortBy = { title: sort };
} else if (req.query.sortBy === &quot;author&quot;) {
sortBy = { author: sort };
} else if (req.query.sortBy === &quot;title&quot;) {
sortBy = { title: sort };
}
const query = [matchQuery,
{
&quot;$sort&quot;: sortBy
},
{
&quot;$facet&quot;: {
&quot;result&quot;: [
{
&quot;$skip&quot;: skip
},
{
&quot;$limit&quot;: limit
},
{
&quot;$project&quot;: {
&quot;createdAt&quot;: 0,
&quot;-createdAt&quot;: 0,
&quot;-updatedAt&quot;: 0,
&quot;-__v&quot;: 0
}
}
],
&quot;count&quot;: [
{
&quot;$count&quot;: &quot;count&quot;
}
]
}
},
{
&quot;$project&quot;: {
&quot;result&quot;: 1,
&quot;count&quot;: {
&quot;$arrayElemAt&quot;: [
&quot;$count&quot;,
0
]
}
}
}]
console.log(JSON.stringify(query))

<!-- end snippet -->

This snippet creates the query si you can simply do const result = await Book.aggregate(query).

Note how the console.log() output is a valid Mongo query in this example

huangapple
  • 本文由 发表于 2023年1月8日 19:49:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/75047500.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定